iOS/Swift Tips 1
1.重写hitTest方法,干预iOS事件传递过程
如下所示,view上有一个button,button一半的frame在父类view bounds之外, 按照iOS系统默认的处理逻辑, 如果点击按钮上半部分,则按钮不会响应时间,如果点击下半部分才行, 要想让点击上半部分一样相应事件,则需要干预事件的传递过程,如下代码所示. 判断事件发生的point在button上面,则让button去响应事件即可.
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的私有方法, 代码如下所示:
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.
7.自定义键盘
自定义键盘其实是想当简单的, 自定义键盘主要是用到了UITextView的inoutView属性, 当给UITextField赋值inputView属性时候, 如果textField获得输入焦点, 则会弹出你设置的键盘View. 至于你想在键盘上画什么东西, 那就是你的事情了, 随便画, 就是这么简单, 没什么好说的.
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, 这里不做详细的探究.
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中也可以自定义编译类型, 并在自己的代码中使用自己的类型, 可以再如下所示的位置设置自定义类型.

10.Swift注释中的TODO & FIXME & ERROR
Xcode也给我们提供了三种实用的简易标记,即 MARK、TODO、FIXME,现在这些在 Objective-C 或者 Swift 环境下都是可以使用的。需要注意的是 MARK、TODO、FIXME 均必须大写,Xcode将会在代码中寻找这样的注释,然后以粗体标签的形式将名称显示在导航栏,就如同我们会用 “#pragma mark -” 符号来标记代码区间一样的道理。
TODO, MARK, FIXME的使用方法如下所示:
//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
- ios10之后, iOS提供了
Timer.scheduledTimer(withTimeInterval: 2, repeats: true, block: <#T##(Timer) -> Void#>)这样的方法,可以更加方便的创建和管理Timer. - 使用Timer也可以完成一些只重复一次的动作, 但是使用GCD的
asyncAfter会不会更好一点呢? - 给tiemr附加一些数据. 使用timer的userinfo.
- 添加一些tolerance. 给timer添加tolerance可以减少电量消耗, 这可能导致你的timer执行不精确,比如每秒重复触发一次的timer, 可以设置tolerance=0.2s(默认情况下tolerance=0,但是系统肯定会默认添加一个很小的数字), 这样tiemr最多可能会延迟0.2s, 但是一定不会提前, 如上所述例子, 第一次timer可能是1..<1.2s内触发,第二次可能2..<2.2触发,第三次可能3..❤️.2触发. 就是说如果第一次延迟, 第二次的触发时间并不会在第一次的基础之上延迟. That is all!
- 把timer添加到RRunloop中. 默认情况下timer会被添加到
defaultRunLoopMode中, 如果用户正在滑动一个tabbleView, 那么你的定时器将不会被触发. 如果你想在用户操作UI的同时定时器也在跑, 则你需要将timer添加到commonModes中. - 如果你想让你的定时器精度非常高, 比如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中的隐士解包
隐士可选值是指那些无论何时使用它们都会自动强制解包的可选值. 目前来看, 在项目中造成隐士可选值的方式有两种:
- 暂时来说, 我们在项目中可能会用到Objective-C方法的返回值或者代理参数, 而在oc中没有一种标记变量是否有值得体系, 默认oc返回值和参数, 我们直接使用时候都会引发隐士解包, 如果oc返回的是nil值, 则在隐士强解包时候回造成程序崩溃.
- 实际上, 在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函数中. 如下所示:
- @convention(swift) : 表明这个是一个swift的闭包;
- @convention(block) :表明这个是一个兼容oc的block的闭包;
- @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
}
iOS/Swift Tips 1的更多相关文章
- iOS swift的xcworkspace多项目管理(架构思想)
iOS swift的xcworkspace多项目管理(架构思想) 技术说明: 今天在这里分享 swift下的 xcworkspace多项目管理(架构思想),能为我们在开发中带来哪些便捷?能为我们对整 ...
- iOS Swift 模块练习/swift基础学习
SWIFT项目练习 SWIFT项目练习2 iOS Swift基础知识代码 推荐:Swift学习使用知识代码软件 0.swift中的宏定义(使用方法代替宏) 一.视图 +控件 1.UIImag ...
- ios swift 实现饼状图进度条,swift环形进度条
ios swift 实现饼状图进度条 // // ProgressControl.swift // L02MyProgressControl // // Created by plter on 7/2 ...
- Building gRPC Client iOS Swift Note Taking App
gRPC is an universal remote procedure call framework developed by Google that has been gaining inter ...
- iOS Swift WisdomScanKit图片浏览器功能SDK
iOS Swift WisdomScanKit图片浏览器功能SDK使用 一:简介 WisdomScanKit 由 Swift4.2版编写,完全兼容OC项目调用. WisdomScanKit的 ...
- iOS Swift WisdomScanKit二维码扫码SDK,自定义全屏拍照SDK,系统相册图片浏览,编辑SDK
iOS Swift WisdomScanKit 是一款强大的集二维码扫码,自定义全屏拍照,系统相册图片编辑多选和系统相册图片浏览功能于一身的 Framework SDK [1]前言: 今天给大家 ...
- iOS Swift WisdomHUD 提示界面框架
iOS Swift WisdomHUD 提示界面框架 Framework Use profile(应用简介) 一:WisdomHUD简介 今天给大家介绍一款iOS的界面显示器:WisdomHUD,W ...
- iOS Swift WisdomKeyboardKing 键盘智能管家SDK
iOS Swift WisdomKeyboardKing 键盘智能管家SDK [1]前言: 今天给大家推荐个好用的开源框架:WisdomKeyboardKing,方面iOS日常开发,优点和功能请 ...
- iOS swift项目IM实现,从长连接到数据流解析分析之Socket
iOS swift项目IM实现,从长连接到底层数据解析分析之Socket 一:项目简介: 去年开始接手了一个国企移动项目,项目的需求是实现IM即时通讯功能. * 一期版本功能包括了: ...
随机推荐
- React Native之React速学教程(下)
概述 本篇为<React Native之React速学教程>的最后一篇.本篇将带着大家一起认识ES6,学习在开发中常用的一些ES6的新特性,以及ES6与ES5的区别,解决大家在学习Reac ...
- 001Java锁之synchronized
01.synchronized & Lock synchronized锁同步 软件层面依赖JVM Lock锁同步 硬件层面依赖cpu指令 02.synchronized作用域 方法:锁住对象实 ...
- RocketMQ读书笔记3——消费者
[不同类型的消费者] DefaultMQPushConsumer 由系统控制读取操作,收到消息后自动调用传入的处理方法来处理. DefaultMQPullConsumer 读取操作中的大部分功能由使用 ...
- c++开发ocx入门实践二
原文:http://blog.csdn.net/yhhyhhyhhyhh/article/details/51374355 IDE:vs2010,c++,测试工具,vs自带的TstCo ...
- Python爬虫教程-18-页面解析和数据提取
本篇针对的数据是已经存在在页面上的数据,不包括动态生成的数据,今天是对HTML中提取对我们有用的数据,去除无用的数据 Python爬虫教程-18-页面解析和数据提取 结构化数据:先有的结构,再谈数据 ...
- IIFE
一.IIFE IIFE:immediately-invoked function expression,即时调用函数表达式. 如果一个函数,在定义的时候,就想直接调用它,就是一个IIFE. 函数执行方 ...
- idea智能提示 不管用 问题
今天碰到个问题,idea的智能提示 死活不能使用了.同一个包下的类竟然还没有智能提示,无语了... 搜了好多篇文章,虽然知道 重装可以解决... 这样设置就可以了:
- sqlserver中循环生成记录
declare @i int set @i=1 while(@i<=10) begin INSERT INTO [BMData].[dbo].[QueryBlackListLogs] ([ID] ...
- SQL Server ->> 时间函数: EOMONTH, DATEFROMPARTS, TIMEFROMPARTS, DATETIMEFROMPARTS, DATETIMEOFFSETFROMPARTS
上面几个函数都是SQL Server 2012新增的时间函数. EOMONTH 返回传入时间的月结束日,返回数据类型为DATE SELECT EOMONTH(GETDATE()) 结果为 DATEFR ...
- Spark 中的宽依赖和窄依赖
Spark中RDD的高效与DAG图有着莫大的关系,在DAG调度中需要对计算过程划分stage,而划分依据就是RDD之间的依赖关系.针对不同的转换函数,RDD之间的依赖关系分类窄依赖(narrow de ...