iOS/Swift Tips 1

1.重写hitTest方法,干预iOS事件传递过程

以下所示,view上有一个button,button一半的frame在父类view bounds以外, 按照iOS系统默认的处理逻辑, 若是点击按钮上半部分,则按钮不会响应时间,若是点击下半部分才行, 要想让点击上半部分同样相应事件,则须要干预事件的传递过程,以下代码所示. 判断事件发生的point在button上面,则让button去响应事件便可.python

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let btnPoint = self.convert(point, to: navigationButton)
        if navigationButton.point(inside: btnPoint, with: event) {
            return navigationButton
        }else{
            return super.hitTest(point, with: event)
        }
    }

2.清空WKWebView的历史纪录

项目需求须要清空webView的历史纪录,要否则只能使用两个webView, 按理说应该是一个就能解决的, 用两个内心有点不爽. 百度两个多小时找不到可用的方法, 最终在stackoverflow上面找到一种解决方案, 使用webWKWebView的私有方法, 代码以下所示:ios

answer in stackoverflow程序员

webView.backForwardList.perform(Selector(("_removeAllItems")))

3.UIScrollView添加约束

//1.把scrollView添加到控制器view
        let scrollView = UIScrollView()
        view.addSubview(scrollView)
        scrollView.snp.updateConstraints { (make) in
            make.edges.equalToSuperview()
        }
        //2.给scrollView添加一个containerView
        let containerView = UIView()
        scrollView.addSubview(containerView)
        containerView.snp.makeConstraints { (make) in
            make.edges.equalTo(scrollView)
            make.width.equalTo(scrollView)
        }
        //3.全部的子控件都放到containerView里面, 在最后一个子控件后设置约束
        containerView.snp.makeConstraints { (make) in
            make.bottom.equalTo(goButton.snp.bottom).offset(20)
        }

4.swift中从nib建立一个控制器

extension UIViewController {
    static func createFromNib() -> Self {
        let className = "\(type(of: self))".split(separator: ".").first
        assert(className != nil, "\n\n \(type(of: self)) -> 找不到对应NIB,请检查nibName是否绑定Class \n\n")
        return self.init(nibName: String(className!), bundle: Bundle.main)
    }
}

5.代码结束应用进程: exit(0)

6.获取应用的惟一标识

早期的ios版本, 能够获取到udid, mac address等信息, 可是ios8以后的版本都获取不到了, UUID虽然能够保证惟一性, 可是没法保证每次获取到的相同. IDFA也能够再不少时候保证惟一性, 可是大概ios10以后, 用户能够选择关闭IDFA追踪, 这时候, 就没法获取到正确的IDFA了. 在网上找了很多的解决方案, 其中最靠谱的是应用第一次打开时候生成惟一的一个UUID,并存储到SSKeyChain中. 而SSKeyChain中的数据能够保证每次重启/重装/应用升级/系统升级都不发生变化. 有说惟一会致使SSKeyChain发生变化的是重置系统和resetchain方法重置KeyChain.web

7.自定义键盘

自定义键盘实际上是想当简单的, 自定义键盘主要是用到了UITextView的inoutView属性, 当给UITextField赋值inputView属性时候, 若是textField得到输入焦点, 则会弹出你设置的键盘View. 至于你想在键盘上画什么东西, 那就是你的事情了, 随便画, 就是这么简单, 没什么好说的.shell

let rect = CGRect(x: 0, y: 0, width: 0, height: 200)
let keyBoardView = CustomKeyBoardView(frame: rect)
textField.inputView = keyBoardView

8.打开应用的权限设置页面

// let url = URL(string: UIApplicationOpenSettingsURLString)
// UIApplication.shared.openURL(url!)

实现自定义键盘的另外一种方案是使用Custom Keyboard Extension, 这里不作详细的探究.swift

9.swift中的预编译选项

Swift 中没有宏的概念,可是提供了 Active Compilation Conditions ,这个设置能够替代以前预编译宏的方式,来作本身的条件编译.闭包

//swift支持的预编译格式
#if <condition>
#elseif <condition>
#else
#endif

//1.检测系统型号或者系统内核类型
// os()只能检测系统类型,而没法检测系统的版本, OSX, iOS
//arch()检测处理器的型号,x86_64, arm, arm64, i386
#if os(OSX)
    typealias Color = NSColor
#elseif os(iOS)
    typealias Color = UIColor
#endif

//2.检测是Debug版本仍是Release版本
#if DEBUG
#elseif Release
#else
#endif

//3.检测swift语言的版本
#if swift(>=3.2)
    // Swift 3.2 及以上
#else
    // Swift 3.2 如下
#endif

//4.检测iOS系统版本, 不一样的系统执行不一样的代码
if #available(iOS 9.0, *) {
    // iOS 9 及以上
}else{
    // iOS 9 如下
}

swift中也能够自定义编译类型, 并在本身的代码中使用本身的类型, 能够再以下所示的位置设置自定义类型.app

10.Swift注释中的TODO & FIXME & ERROR

Xcode也给咱们提供了三种实用的简易标记,即 MARK、TODO、FIXME,如今这些在 Objective-C 或者 Swift 环境下都是可使用的。须要注意的是 MARK、TODO、FIXME 均必须大写,Xcode将会在代码中寻找这样的注释,而后以粗体标签的形式将名称显示在导航栏,就如同咱们会用 “#pragma mark -” 符号来标记代码区间同样的道理。async

TODO, MARK, FIXME的使用方法以下所示:ide

//TODO: 标记未来要完成的内容
//MARK: 标记一件事情
//FIXME: 标记之后要修正或完善的内容
//ERROR:标记一段错误

然而, 问题来了, 虽然可以标记, 可是若是不是警告⚠️或者错误❌, 程序员们常常会忘记本身标记的这些东西. 经常会发现本身几个月前标记的问题, 结果拖了几个月都记不起来. 那么该怎么解决呢? 答案就是在run script build phases添加一段编译脚本.

1.切换到:target-->build phases-->editor-->add run script build phases;
2.选择:New Run Script Parse;
3.添加以下shell脚本:;

TAGS="TODO:|FIXME:|WARNING:"
ERRORTAG="\/\/ERROR:|\/\/ ERROR:"
find "${SRCROOT}" \( -name "*.h" -or -name "*.m" -or -name "*.swift" \) -and -type f -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$|($ERRORTAG).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"| perl -p -e "s/($ERRORTAG)/ error: \$1/"

Prefect!

11. 一些关于swift的tips

//tip1: we can combine protocols and subtypes with `&`, ex:
protocol A {}
class C {}
class D: C, A {}
func someFunc(using: A & C){}
someFunc(using: D())

//tip2: Protocol extensions can provide default property values
protocol Fadeable {
    var fadeSpeed: TimeInterval {get}
    func fadeOut()
}

extension Fadeable where Self: UIView {
    var fadeSpeed: TimeInterval {return 0.25}
    func fadeOut() {
        UIView.animate(withDuration: fadeSpeed) {
            self.alpha = 0
        }
    }
}
class CustomView: UIView, Fadeable {}

//tip3: Use destructing to manipulate tuple
// 元组能够当成参数返回值被直接返回; 元祖能够经过"="直接解构到变量上; 甚至能够用来交换两个变量
func getGredentials() -> (String, String) {return ("swift", "python")}
let someStrs = getGredentials()     //直接使用一个变量接收
var (str1, str2) = getGredentials() //使用元祖接收
var (someStr1, someStr2) = someStrs //直接使用两个变量组成的元组解构另外一个元组变量
(str2, str1) = (str1, str2)         //交换两个变量的值

//tip4: public getter, private setter
// `public private(set)`, This lets us mark a property as being open for reading but closed for writing
struct Bank {
    public private(set) var address: String
}

//tip5: 定义本身的init可是不覆盖系统提供的初始化方法
//在结构体中, 若是提供本身的init方法, 则系统再也不提供, 这是为了防止你在你本身的初始化方法中作了重要的操做, 而
//  这时候若是系统继续提供初始化方法, 则会忽略你的重要初始化.若是你想要在本身提供初始化方法时候保留系统提供的
//  初始化方法, 则能够在extension中提供初始化方法.
//在class中, 一样的道理, 你在extension中提供的init方法不会覆盖掉系统提供的init方法. 和结构体不一样的是, 类
//  扩展中的init方法须要使用`convenience`声明.

//tip6: static和class的区别
// static和class都可以用来声明类变量和类方法, 他们的区别在于继承上. class声明的变量和方法能够被子类继承, 而static
//  声明的不可以被继承.

//tip7: `==`和`===`的区别
// ==表示比较两个值是否相等
// ===表示比较两个变量的内存地址是否相等

12.timer

  1. ios10以后, iOS提供了Timer.scheduledTimer(withTimeInterval: 2, repeats: true, block: <#T##(Timer) -> Void#>)这样的方法,能够更加方便的建立和管理Timer.
  2. 使用Timer也能够完成一些只重复一次的动做, 可是使用GCD的asyncAfter会不会更好一点呢?
  3. 给tiemr附加一些数据. 使用timer的userinfo.
  4. 添加一些tolerance. 给timer添加tolerance能够减小电量消耗, 这可能致使你的timer执行不精确,好比每秒重复触发一次的timer, 能够设置tolerance=0.2s(默认状况下tolerance=0,可是系统确定会默认添加一个很小的数字), 这样tiemr最多可能会延迟0.2s, 可是必定不会提早, 如上所述例子, 第一次timer多是1..<1.2s内触发,第二次可能2..<2.2触发,第三次可能3..<3.2触发. 就是说若是第一次延迟, 第二次的触发时间并不会在第一次的基础之上延迟. That is all!
  5. 把timer添加到RRunloop中. 默认状况下timer会被添加到defaultRunLoopMode中, 若是用户正在滑动一个tabbleView, 那么你的定时器将不会被触发. 若是你想在用户操做UI的同时定时器也在跑, 则你须要将timer添加到commonModes中.
  6. 若是你想让你的定时器精度很是高, 好比60次/秒, 则此时timer是没法知足需求的, timer只能用在精度相对比较低的场景, 若是你有经度比较高的需求, 你可使用CADisplayLink.
//使用timer的userinfo. 给timer附加一些数据, Dict类型的数据, 能够放在参数context中, 以下所示:
let context = ["user": "@twostraws"]
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: context, repeats: true)
//在timer对象中能够取出timer.userinfo,以下所示
@objc func fireTimer(timer: Timer) {
    guard let context = timer.userInfo as? [String: String] else { return }
    let user = context["user", default: "Anonymous"]
    
    print("Timer fired by \(user)!")
    runCount += 1
    
    if runCount == 3 {
        timer.invalidate()
    }
}

//使用commonModes
let context = ["user": "@twostraws"]
let timer = Timer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: context, repeats: true)
RunLoop.current.add(timer, forMode: .commonModes)

摘自: https://www.hackingwithswift.com/articles/117/the-ultimate-guide-to-timer

13.UIActivityViewController,自定义分享的activityAction

详情参考: https://www.hackingwithswift.com/articles/118/uiactivityviewcontroller-by-example
demo参考测试代码库.

14.swift4.0以后,App中自定义UIApplication

1. 建立自定义的UIApplication子类MyApplication;
2. 建立main.swift文件;
3. 在自定义的main.swift中指定初始化的MyApplication;
let _ = UIApplicationMain(
    CommandLine.argc,
    UnsafeMutableRawPointer(CommandLine.unsafeArgv)
        .bindMemory(
            to: UnsafeMutablePointer<Int8>.self,
            capacity: Int(CommandLine.argc)
    ),
    NSStringFromClass(WHApplication.self),
    NSStringFromClass(AppDelegate.self)
)
4.删除原来Appdelegate中的`@UIApplicationMain`注解.  Done.

15. swift中的隐士解包

隐士可选值是指那些不管什么时候使用它们都会自动强制解包的可选值. 目前来看, 在项目中形成隐士可选值的方式有两种:

  1. 暂时来讲, 咱们在项目中可能会用到Objective-C方法的返回值或者代理参数, 而在oc中没有一种标记变量是否有值得体系, 默认oc返回值和参数, 咱们直接使用时候都会引起隐士解包, 若是oc返回的是nil值, 则在隐士强解包时候回形成程序崩溃.
  2. 实际上, 在swift中也能够定义隐士解包的值, 如var str: String!, 这种状况下, 若是直接使用str, 则会形成程序崩溃. 在实际开发中, 要避免这种写法.

虽然隐士可选值在行为上和非可选值同样, 可是咱们依然能够对它们使用可选链, nil合并, if let和map, 全部的操做都和可选值同样.

16. swift线程锁

16.1 synchronized

func synchronized(lock: AnyObject, closure: () -> ()) {
        objc_sync_enter(lock)
        closure()
        objc_sync_exit(lock)
    }

16.2 dispatch_semaphore

使用dispatch_semaphore建立信号, 使用信号量来控制访问线程的数量.

17. 动态加载字体

/// 判断字体是否存在
    private static func isFontLoaded(fontName: String) -> Bool {
        let tmpFont = UIFont(name: fontName, size: 10)
        return tmpFont?.fontName == fontName
    }
    
    /// 动态加载字体
    private static func dynamicLoadFont(fontName: String) {
        guard var fontPath = Bundle.main.path(forResource: "UIFoundationKit.bundle", ofType: nil) else { return }
        fontPath = "\(fontPath)/\(fontName.uppercased()).OTF"
        let url = URL(fileURLWithPath: fontPath)
        if
            let fontData = try? Data(contentsOf: url),
            let provider = CGDataProvider(data: fontData as CFData),
            let font = CGFont.init(provider) {
            CTFontManagerRegisterGraphicsFont(font, nil)
        }
    }

18. swift中的@convention

@convention是用来修饰闭包的, 用来代表此闭包能够兼容什么语言格式的闭包. 好比convention(c)表示此闭包能够传递到c函数中. 以下所示:

  1. @convention(swift) : 代表这个是一个swift的闭包;
  2. @convention(block) :代表这个是一个兼容oc的block的闭包;
  3. @convention(c) : 代表这个是兼容c的函数指针的闭包。

使用示例:

let saySomething_c : @convention(c) (String)->Void = {
    print("i said: \($0)")
}

let saySomething_oc : @convention(block) (String)->Void = {
    print("i said: \($0)")
}

let saySomething_swift : @convention(swift) (String)->Void = {
    print("i said: \($0)")
}

19.swift获取对象内存地址

let obj = Obj()
/// 方案一: 测试中 发现做用在<引用类型>的对象上能确保正确性
let point = Unmanaged<AnyObject>.passUnretained(obj as AnyObject).toOpaque()
let hashValue = point.hashValue // 这个就是惟一的,能够做比较

/// 方案二:测试中 发现做用在<值类型>的对象上能确保正确性
let hashValue2 = withUnsafePointer(to: &obj) { (point) -> Int in
        /// 闭包的实现有多种,可根据本身需求修改 
        return point.hashValue
 }
相关文章
相关标签/搜索