iOS | 技术点小记4

获取app top view controller

+ (UIViewController * _Nullable)getAppTopViewController {
    UIWindow *window = [self getAppKeyWindow];
    if(!window) {
        NSLog(@"Not found a key window for application: %@!!!", UIApplication.sharedApplication);
        return nil;
    }
    UIViewController *vc = [window rootViewController];
    while ([vc presentedViewController]) {
        vc = [self _getTopViewControllerOf:vc.presentedViewController];
    }
    return vc;
}

+ (UIViewController * _Nullable)_getTopViewControllerOf: (UIViewController *)vc {
    if ([vc isKindOfClass:[UINavigationController class]]) {
        return [self _getTopViewControllerOf:[(UINavigationController *)vc visibleViewController]];
    }
    else if ([vc isKindOfClass:[UITabBarController class]]) {
        return [self _getTopViewControllerOf:[(UITabBarController *)vc selectedViewController]];
    }
    return vc;
}

获取app key window

+ (UIWindow * _Nullable)getAppKeyWindow {
    UIWindow *window = nil;
    if (@available(iOS 13.0, *)) {
        UIWindowScene *sc = (UIWindowScene *)UIApplication.sharedApplication.connectedScenes.anyObject;
        if (@available(iOS 15.0, *)) {
            window = sc.keyWindow;
        }
        else {
            window = sc.windows.firstObject;
        }
    }
    else {
        window = [UIApplication.sharedApplication.delegate respondsToSelector:@selector(window)]
        ? UIApplication.sharedApplication.delegate.window
        : UIApplication.sharedApplication.keyWindow;
    }
    return window;
}

自定义格式化方法

- (NSString *)stringWithFormat:(NSString *)format, ...{
    if(![format isKindOfClass:NSString.class]) {
        format = [NSString stringWithFormat:@"%@",format];
    }
    va_list args;
    va_start(args, format);
    NSString *result = [[NSString alloc] initWithFormat:format arguments:args];
    va_end(args);
    return result;
}

UINavigationController

当设置nav controller的view.tintColor = .red时,会将title、barButtonItem的tintColor都设置为该色。

Swift高斯模糊

radius有效范围为0 ~ min(image.width, image.height)/image.scale

import CoreImage.CIFilterBuiltins

extension UIImage {
    func blurredImage(radius: Float) -> UIImage? {
        guard let cgImg = cgImage else {
            return nil
        }
        let ci = CIImage(cgImage: cgImg)
        let gaussFilter = CIFilter.gaussianBlur()
        gaussFilter.inputImage = ci
        gaussFilter.radius = radius
        guard let ciImg = gaussFilter.outputImage?.cropped(to: ci.extent) else {
            return nil
        }
        print(radius)
        return .init(ciImage: ciImg)
    }
}

更优雅的handlerreturn

enum RequestError: Error {
    case noData
}

func mockRequest(url: String, completion: @escaping (Result)->Void) {
    guard let aURL = URL(string: url) else {
// 这里推荐这样的写法,也可以将`completion`和`return`分开写
        return completion(.failure(URLError(.badURL)))
    }
    let task = URLSession.shared.dataTask(with: aURL) { data, response, error in
        if let error = error {
            return completion(.failure(error))
        }
        guard let data = data else {
            return completion(.failure(RequestError.noData))
        }
        completion(.success(data))
    }
    task.resume()
}

mockRequest(url: getOneImageUrl) { result in
    switch result {
    case .failure(let error): print("Mock request failed: ", error)
    case .success(let data):
        print("Mock request success: ", data.count)
    }
}

Swift 5.5 try await/async

1,使用async await,进行异步串行执行。
2,使用async let,在同一个Task(任务)内,进行异步并行执行。
3,使用group task和 for await,让多个Task并行执行。

Swift Pointer

var x = 42
var y = 3.14
var z = "foo"
var obj = NSObject()

func printPointer(ptr: UnsafePointer) {
    print(ptr)
}

printPointer(ptr: &x)
printPointer(ptr: &y)
printPointer(ptr: &z)
printPointer(ptr: &obj)

// 0x000000011a145660
// 0x000000011a145668
// 0x000000011a145670
// 0x000000011a145688

Swift semaphore lock

fileprivate var lock: DispatchSemaphore = DispatchSemaphore(value: 1)

    public func cancel() {
        _ = lock.wait(timeout: DispatchTime.distantFuture)
        defer { lock.signal() }
        guard !isCancelled else { return }
        isCancelled = true
        cancelAction()
    }

Swift 限制set/get

// 外部`get`,内部`set`
public fileprivate(set) var isCancelled = false

Swift case let as

switch progressAlamoRequest {
case let downloadRequest as DownloadRequest:
case let uploadRequest as UploadRequest:
case let dataRequest as DataRequest:
default: break                
}

NSTextAttachment 对齐

let textAttachment = NSTextAttachment()
textAttachment.bounds = .init(x: 0, y: round(label.font.capHeight - 30)/2.0, width: 30, height: 30)

Swift enum if

let a = Animal.people("张三")
// 注意是=而不是==,类似 if let _ = a,是赋值操作。
if case .people(let name) = a {
     print(name)
}
// 省略参数
if case .people = a {
}

// 只要枚举里带参数了,不带参数类型也需要使用case这样判断,也是=,而使用.other == a,就会报错。有意思。
if case .other = a {
    print(name)
}

Swift 数字格式化

String(format: "%.2f", floatValue)

UIPanGestureRecognizer

let gestureIsDraggingFromLeftToRight = (recognizer.velocity(in: view).x > 0)

Swift 5 return issue

`Not` return
`Ok` return

AutoLayout

但在实际应用中,一般会用leadingAnchor和trailingAnchor来代替leftAnchor和rightAnchor。它们的区别在于,在一些喜欢从右往左阅读的地区,leadingAnchor实际代表右边,而trailingAnchor代表左边。在我国这种从左往右阅读的国家则可以认为leading==left,trailing==right。比起直接使用left和right,使用leading和trailing可以免去做适配的麻烦。这也是Auto Layout的优势之一。

button.translatesAutoresizingMaskIntoConstraints = NO;
translatesAutoresizingMaskIntoConstraints是为了兼容前朝遗老Auto Resizing所做出的遗憾逻辑。它会把Auto Resizing中描述的关系转化成Auto Layout中的约束。如果你要自己添加约束,就必须把这个字段设为NO。

Git set-url

git remote set-url origin [email protected]/user_name/repository_name.git

Swift Date funcs

extension Date {
    fileprivate static let zone = Calendar.current.identifier
    
    var startOfDay: Date {
        return Calendar.current.startOfDay(for: self)
    }
    
    var startOfMonth: Date {
        let calendar = Calendar(identifier: Self.zone)
        let components = calendar.dateComponents([.year, .month], from: self)
        
        return calendar.date(from: components) ?? Date()
    }
    
    var startOfWeek: Date {
        let calendar = Calendar(identifier: Self.zone)
        let components = calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self)
        
        return calendar.date(from: components) ?? Date()
    }
    
    var startOfYear: Date {
        let calendar = Calendar(identifier: Self.zone)
        let components = calendar.dateComponents([.year], from: self)
        
        return calendar.date(from: components) ?? Date()
    }
    
    var endOfDay: Date {
        var components = DateComponents()
        components.day = 1
        components.second = -1
        return Calendar.current.date(byAdding: components, to: startOfDay) ?? Date()
    }
    
    var endOfMonth: Date {
        var components = DateComponents()
        components.month = 1
        components.second = -1
        return Calendar(identifier: Self.zone).date(byAdding: components, to: startOfMonth) ?? Date()
    }
    
    var endOfWeek: Date {
        var components = DateComponents()
        components.weekOfYear = 1
        components.second = -1
        return Calendar(identifier: Self.zone).date(byAdding: components, to: startOfWeek) ?? Date()
    }
    
    var endOfYear: Date {
        var components = DateComponents()
        components.year = 1
        components.second = -1
        return Calendar(identifier: Self.zone).date(byAdding: components, to: startOfYear) ?? Date()
    }
}
}
enum ReportRange: String, CaseIterable {
    case daily = "Today"
    case monthly = "This Month"
    case weekly = "This Week"
    case yearly = "This Year"
    
    func timeRange() -> (Date, Date) {
        let now = Date()
        switch self {
        case .daily:
            return (now.startOfDay, now.endOfDay)
        case .weekly:
            return (now.startOfWeek, now.endOfWeek)
        case .monthly:
            return (now.startOfMonth, now.endOfMonth)
        case .yearly:
            return (now.startOfYear, now.endOfYear)
        }
    }

CaseIterable allows you to iterate over the possible values of the enum you just defined. 可以访问.allCases

刻度绘制

Marker
// Counter View markers
guard let context = UIGraphicsGetCurrentContext() else {
  return
}
  
// 1 - Save original state
context.saveGState()
outlineColor.setFill()
    
let markerWidth: CGFloat = 5.0
let markerSize: CGFloat = 10.0

// 2 - The marker rectangle positioned at the top left
let markerPath = UIBezierPath(rect: CGRect(
  x: -markerWidth / 2, 
  y: 0, 
  width: markerWidth, 
  height: markerSize))

// 3 - Move top left of context to the previous center position  
context.translateBy(x: rect.width / 2, y: rect.height / 2)
    
for i in 1...Constants.numberOfGlasses {
  // 4 - Save the centered context
  context.saveGState()
  // 5 - Calculate the rotation angle
  let angle = arcLengthPerGlass * CGFloat(i) + startAngle - .pi / 2
  // Rotate and translate
  context.rotate(by: angle)
  context.translateBy(x: 0, y: rect.height / 2 - markerSize)
   
  // 6 - Fill the marker rectangle
  markerPath.fill()
  // 7 - Restore the centered context for the next rotate
  context.restoreGState()
}

// 8 - Restore the original state in case of more painting
context.restoreGState()

In the code above, you:

  • Save the original state of the matrix before you manipulate the context's matrix.
  • Define the position and shape of the path, though you're not drawing it yet.
  • Move the context so that rotation happens around the context's original center, indicated by the blue lines in the previous diagram.
    Save the centered context state for each mark.
  • Determine the angle for each marker using the individual angle previously calculated. Then you rotate and translate the context.
  • Draw the marker rectangle at the top left of the rotated and translated context.
  • Restore the centered context's state.
  • Restore the original state of the context before any rotations or translations.

Code Separate Guide

class MyViewController: UIViewController {
  // class stuff here
}

// MARK: - UITableViewDataSource
extension MyViewController: UITableViewDataSource {
  // table view data source methods
}

// MARK: - UIScrollViewDelegate
extension MyViewController: UIScrollViewDelegate {
  // scroll view delegate methods
}

Xcode Copy Method Signature

put the cursor in the method name and press Shift-Control-Option-Command-C (all 4 modifier keys) and Xcode will kindly put the signature on your clipboard.

class FilmTableViewCell: UITableViewCell {
  func configure(
    title: String,
    plot: String,
    isExpanded: Bool,
    poster: String
  ) {
...
  }
}

//copied: FilmTableViewCell.configure(title:plot:isExpanded:poster:)

Xcode Playground

Creating a new playground. In Xcode, go to File ▸ New ▸ Playground

Swift @autoclosure

简化闭包/自动闭包

override func viewDidLoad() {
        super.viewDidLoad()
        
        test(a: 2>3, b: 3>4)
// also likes:
// test(a: 2>3, b: {3>4})
    }
    
    func test(a: Bool, b: @autoclosure ()->Bool) -> Bool {
        if a {
            return a
        }
        return b()
    }

举例

func calculate(_ operating: @autoclosure ()->R) -> R {
    operating()
}

calculate(2*3+4)

Swift init

  • OC的初始化方法,它必须返回一个instancetype类型的self实例,而这个self只能通过super的init方法初始化才行。

  • 而Swift的初始化方法,既然被称为designated,就说明他是自己内部实现了自己的初始化逻辑,脱离了super的必须调用限制。

  • 在Swift的初始化过程中,先保证自己内部的所有属性已经全部有了初始值(optional的除外),然后调用super的init方法告知自己的父类完成它的初始化过程,这样一级一级向上告知后,就能保证整个继承链下所有的类的属性是有初始值的,才能正确完成初始化过程,进一步优化内存的初始化。

  • 由于convenience初始化方法可以被extension,所以它内部的内存分配处理过程必须由它内部已经存在的designated初始化方法来实现。

  • 便捷初始化方法必须调用本类指定初始化方法

    • A convenience initializer must ultimately call a designated initializer.
  • 指定初始化方法必须调用父类指定初始化方法

    • 重写父类 init() 必须先调用super.init(),然后赋值
    • 实现空白 init() 则先赋值,后调用super.init()
  • 要求初始化方法

    • 子类必须实现
    • You don’t write the override modifier when overriding a required designated initializer

.rpa文件解压

  • 安装python
  • 安装pip
curl https://bootstrap.pypa.io/get-pip.py | python3
  • 安装unrpa
pip3 install unrpa  
  • 开始解压
python3 -m unrpa -mp "/path/folder" "/path/XXX.app/Contents/Resources/autorun/game/images.rpa"

Windows端口占用

  • netstat -aon|findstr "8085"
  • tasklist|findstr "14572"
  • task >taskkill /T /F /PID the port pid number

端口占用

  • sudo lsof -i:(port)
  • sudo kill (PID)

Swift tool-chain下载

https://www.swift.org/download

Xcode 版本下载

https://download.developer.apple.com/Developer_Tools/Xcode_12.5.1/Xcode_12.5.1.xip

xcodebuild 使用系统git终端代理解决github慢问题

xcodebuild -resolvePackageDependencies -scmProvider system

PublicAIPs

https://github.com/public-apis/public-apis

iOS15导航栏适配

if (@available(iOS 15.0, *)) {
        UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
        appearance.titleTextAttributes = @{NSForegroundColorAttributeName :UIColor.greenColor};
        appearance.backgroundColor = UIColor.redColor;
        
        appearance.buttonAppearance.normal.titleTextAttributes = @{NSForegroundColorAttributeName: UIColor.greenColor};
        appearance.doneButtonAppearance.normal.titleTextAttributes = @{NSForegroundColorAttributeName: UIColor.greenColor};
        appearance.backButtonAppearance.normal.titleTextAttributes = @{NSForegroundColorAttributeName: UIColor.yellowColor};
        
        self.navigationBar.scrollEdgeAppearance = appearance;
        self.navigationBar.standardAppearance = appearance;
    }

Xcode低版本调试真机高版本

  • 复制高版本Xcode对应的iPhoneOS15.0.sdk到低版本Xcode的目录/Applications/Xcode12.5.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs
  • 复制15.0对应的文件到低版本Xcode目录/Applications/Xcode12.5.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport

Docker

https://kapeli.com/dash

免费icons

https://icons8.com

Font Bold Text

UIFontWeightUltraLight > UIFontWeightThin
UIFontWeightThin > UIFontWeightLight
UIFontWeightLight > UIFontWeightRegular
UIFontWeightRegular > UIFontWeightSemibold 
UIFontWeightMedium > UIFontWeightBold
UIFontWeightSemibold > UIFontWeightHeavy
UIFontWeightBold > UIFontWeightBlack
UIFontWeightHeavy > UIFontWeightBlack
UIFontWeightBlack > UIFontWeightBlack

免费工具网站

https://juejin.cn/post/7010397195157372942

网易云音乐API

https://neteasecloudmusicapi.vercel.app

PicGo + Gitee 配置图床

PlanUML

https://plantuml.com/zh

免费图片api

https://picsum.photos

Swift 规范

https://juejin.cn/post/7017740209236213791

国内网速测试

http://www.17ce.com/site

UserDefaults 下标

// UserDefaults.standard[key] = value

extension UserDefaults {
    subscript(key: String) -> Any? {
        set{
            setValue(newValue, forKey: key)
            synchronize()
        }
        get{
            return value(forKey: key)
        }
    }
}

汉字笔画库

HanZiWriterhttps://hanziwriter.org/docs.html

Jasper 入门

https://blog.csdn.net/dullchap/article/details/51799070

深入iOS系统底层之crash解决方法

https://juejin.cn/post/6844903670404874254

iOS设备升/降级

https://www.163.com/dy/article/GDVG8MBH05373GLF.html

大陆身份证号计算规则

计算方法
1、将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7-9-10-5-8-4-2-1-6-3-7-9-10-5-8-4-2。
2、将这17位数字和系数相乘的结果相加。
3、用加出来和除以11,看余数是多少?
4、余数只可能有0-1-2-3-4-5-6-7-8-9-10这11个数字。其分别对应的最后一位身份证的号码为1-0-X -9-8-7-6-5-4-3-2。(即余数0对应1,余数1对应0,余数2对应X...)
5、通过上面得知如果余数是3,就会在身份证的第18位数字上出现的是9。如果对应的数字是2,身份证的最后一位号码就是罗马数字X。
例如:某男性的身份证号码为【53010219200508011X】, 我们看看这个身份证是不是符合计算规则的身份证。
首先我们得出前17位的乘积和【(5*7)+(3*9)+(0*10)+(1*5)+(0*8)+(2*4)+(1*2)+(9*1)+(2*6)+(0*3)+(0*7)+(5*9)+(0*10)+(8*5)+(0*8)+(1*4)+(1*2)】是189,然后用189除以11得出的结果是189÷11=17余下2,187÷11=17,还剩下2不能被除尽,也就是说其余数是2。最后通过对应规则就可以知道余数2对应的检验码是X。所以,可以判定这是一个正确的身份证号码。

Xcode已损坏,无法打开,您应该将其移至废纸篓

xattr -rc /Applications/Xcode.app

递归获取满足条件subview

extension UIView {
    func subviews(where condition: (UIView) throws -> Bool) rethrows -> [UIView] {
        var subs = try subviews.filter(condition)
        for sub in subviews {
            let matches = try sub.subviews(where: condition)
            subs.append(contentsOf: matches)
        }
        return subs
    }
}

16进制颜色转换


    static func _hexColor(_ hexString: String) -> UIColor? {
        var string = ""
        if hexString.lowercased().hasPrefix("0x") {
            string =  hexString.replacingOccurrences(of: "0x", with: "")
        } else if hexString.hasPrefix("#") {
            string = hexString.replacingOccurrences(of: "#", with: "")
        } else {
            string = hexString
        }

        if string.count == 3 { // convert hex to 6 digit format if in short format
            var str = ""
            string.forEach { str.append(String(repeating: String($0), count: 2)) }
            string = str
        }

        guard let hexValue = Int(string, radix: 16) else { return nil }

        let red = (hexValue >> 16) & 0xff
        let green = (hexValue >> 8) & 0xff
        let blue = hexValue & 0xff
        return .init(red: CGFloat(red)/255, green: CGFloat(green)/255, blue: CGFloat(blue)/255, alpha: 1)
    }

百度地图SDK

https://lbsyun.baidu.com/index.php?title=iossdk/guide/

高德地图SDK

https://lbs.amap.com/api/ios-sdk/guide/
唤起:https://lbs.amap.com/api/amap-mobile/guide/ios/route

Moya URL编码问题

https://github.com/Moya/Moya/issues/1049

UIViewController的touchesBegan事件处理问题

处理弹出对话框时,蒙层点击消失,内容区域点击不处理

    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)

        if !canMaskViewDismissable {
            return
        }

        var isTouchInner = false
        let list = event?.allTouches?.filter{ $0.view != nil } ?? []
        for t in list
        {
            let loc = t.preciseLocation(in: contentView)
            print(loc)
            if contentView.bounds.contains(loc) {
                isTouchInner = true
                break
            }
        }

        if canMaskViewDismissable, !isTouchInner {
            close()
        }
    }

Fastlane打包上传蒲公英

安装:sudo install fastlane -NV
工程目录下:fastlane init
编辑:Fastfile

default_platform(:ios)

platform :ios do
    puts "============ 打包开始 ==========="
    lane :pkg do |options|

    version = get_info_plist_value(path: "./YourApp/Supports/Info.plist", key: "CFBundleVersion")
    config = options[:to]
    channel = "development"
    env = "Debug"
    
    if config == "appstore"
        channel = "app-store"
        env = "Release"
    end

    gym(
        configuration: "#{env}",
        export_method: "#{channel}",
        silent: true,
        clean: true,
        scheme: "RaveLand",
        output_name: "YourApp_#{channel}_#{version}.ipa",
        output_directory: "./build",
    )
    
    if config == "pgy"
        puts "============ 开始上传蒲公英 ============ "
        pgyer(api_key: "", user_key: "")
        puts "============ 上传蒲公英成功 ============ "
    end
            
    puts "============ 打包完成 ============ "
    system "open ./build"
    
    end
end

蒲公英插件:fastlane add_plugin pgyer
打包:fastlane pkg

Github更新开源库

git add .
git commit -m 'MESSAGE'
git push
git tag x.x.x
git push --tags

pod trunk register EMAIL "USERNAME"
pod trunk push .podspec --allow-warnings --verbose

你可能感兴趣的:(iOS | 技术点小记4)