很多不常用的简单的 API 的使用方式。分开写的话太短小,就合在一起吧。(这里都是学到的时候,东找一点西找一点拼凑起来的,一开始没有记录出处在哪,如果哪里有侵权的地方,还请尽快告知呀。我会第一时间注明出处的。谢谢啦~)
NSUndoManager
NSUndoManger 是苹果对于命令模式的一种封装,用来撤销历史命令。共有两种撤销操作,简单的以 selector 为基础的撤销和复杂的以 NSInvocation 为基础的撤销。
撤销操作
注册一个简单撤销操作
我们可以用 registerUndoWithTarget:selector:object: 注册一个撤销操作,保存撤销时会执行的方法和参数:
1 | - (void)updateScore:(NSNumber*)score { |
上面将改变前的 myMoview.score 通过撤销方法保存了起来。另外 setActionName: 指定撤销操作的名词。
使用 NSInvocation 注册复杂撤销操作
简单撤销不能应对多参数的情况,所以要使用 NSInvocation ,调用 prepareWithInvocationTarget: 记录哪些对象会接收哪些发生改变的消息:
1 | - (void)movePiece:(ChessPiece*)piece toRow:(NSUInteger)row column:(NSUInteger)column { |
NSUndoManager 对象本身并没有上面的 movePiece:ToRow:column: 方法。是通过 forwardInvocation: 将消息转发至相应对象的。
将动作组合在一起
上面只能撤消一个方法,如果要撤销多个操作呢?
1 | - (void)readAndArchiveEmail:(Email*)email { |
通过 beginUndoGrouping 和 endUndoGrouping 将多个分离的撤销操作组合在一起。
实现一次撤销
iOS 摇晃手势
默认情况下,用户通过摇晃设备来触发撤销操作。如果一个 view controller 需要处理一个撤销请求,那么这个 view controller 必须:
- 能成为 first responder
- 一旦页面显示(view appears),即变成 first responder
- 一旦页面消失(view disappears),即放弃 first responder
当 view controller 接收到运动事件,当撤销或重做可用时,系统会展示给用户一个会话界面。View controller 的 undoManager 属性不需要其他操作就可以响应用户的选择。
1 | @implementation ViewController |
执行撤销
执行撤销操作的时候,系统会将撤销栈中的对象 pop 出来,然后执行。通过 undo 方法触发:
1 | if ([self.undoManager canUndo]) { |
清空撤销栈
有时候我们需要手动清空撤销栈。通常情况下当上下文发生戏剧性变化时,比如说 iOS 上改变了显示的 view controller 或一个打开的文档外部发生了变化。此时,撤销管理器的栈可以通过 removeAllActions 来清空或使用 removeAllActionsWithTarget: 清空某一个对象的所有撤销方法。
撤销与恢复
如果有一对相反的方法需要表示既能撤销也能恢复,需要这样使用:
1 | - (void)addItem:(id)item { |
在恢复中注册撤销,在撤销中注册恢复。这里先判断 isUndoing 其实没有太大必要,去掉也没什么问题。
NSInvocation
当我们想要动态调用某一个方法的时候,我们一般会选择 performSelector:withObject:withObject 方法。但是这个方法有一个局限就是最多只能调用含有两个参数的函数:
1 | NSString *sample = [self performSelector:@selector(append:withStr:) withObject:@"a" withObject:@"b"]; |
苹果提供了另外一种方法:NSInvocation。下面介绍一下使用步骤:
提供方法签名
首先要获得调用方法的方法签名:
1 | //NSObject的对象方法,任何继承自NSObject的对象都可以调用 |
1 | NSString *methodNameStr = @"test:withArg2:andArg3:" |
使用方法签名创建一个 NSInvocation 对象
1 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; |
设置调用对象和调用方法
invocation 对象有两个属性,执行对象 target,执行的方法 selector:
1 | invocation.target = self; |
设置参数
使用 setArgument:atIndex: 方法设置参数。参数从2开始,因为0、1被 target 和 selector占用了:
1 | NSString *arg1 = @"a"; |
注意,这里是使用的是参数的引用,传递的是地址。
执行方法
直接执行方法:
1 | [invocation invoke]; |
如果方法有返回值呢?在上面的语句执行方法后,我们通过 getReturnValue: 方法拿到返回值:
1 | //可以在invoke方法前添加,也可以在invoke方法后添加 |
方法签名有两个只读属性,一个是 numberOfArguments 表示方法参数的个数;还有个就是上面代码涉及的 methodReturnLength 表示方法返回值类型的长度,大于0表示有返回值。

NSJSONSerialization
JSON(也就是特定类型的 NSString) 和 NSDictionary、NSArray 之间的转换可以通过 NSJSONSerialization 类进行
JSON(NSString) => NSDictionary/NSArray
先将 JSON 通过 dataUSingEncoding: 转换为 NSData,然后再用通过 NSJSONSerialization 将 NSData 转换为 NSDictionary/NSArray.
1 |
|
使用:
1 | // 数组 |
数组的 JSON 是用 [] 包裹起来的。这个例子中每个元素都是单元素的 NSDictionary。字典的 JSON 就是 {} 括起来的键值对。注意由于返回时是 id 类型,不区分具体是 NSDictionary 还是 NSArray,所以要进行类型转换
NSDictionary/NSArray => JSON(NSString)
在 NSObject 中添加分类,先将 NSDictionary/NSArray 转换为 NSData。注意上面使用的方法是 JSONObjectWithData:options:error: 这里是 dataWithJSONObject:options:error:。然后通过 initWithData:encoding: 将 NSData 转为 NSString:
1 |
|
NSString 与 NSArray 的互转
上面的 JSON 是一种特殊格式的 NSString,所以要借助于 NSJSONSerialization 进行解析。但是如果直接的 NSString 和 NSArray 的互相转换就要简单许多,但是还有注意点.
一般我们把 NSArray 转为 NSString 是直接通过 stringWithFormat: 的形式:
1 | NSArray *array = [NSArray arrayWithObjects:@"sss",@"mmm",@"lll",@"kkk",@"ppp",@"ooo", nil]; |
可以看出,这样的转换是有问题的,中间引入了空格,并且两边还有括号没有消除。👇是正确的方式:
1 | NSArray *array = [NSArray arrayWithObjects:@"sss",@"mmm",@"lll",@"kkk",@"ppp",@"ooo", nil]; |
通过 NSArray 的方法,将数组中的元素完全拿了出来。
另一方面, NSString 转为 NSArray。通过 NSString 的 componentsSeparatedByString: 方法,识别逗号:
1 | NSArray *array2 = [str1 componentsSeparatedByString:@","]; |
比较输出结果可以发现,str1 无法重新转回最开始的数组了,所以两者互转一定要用 str2 的方式:

UIView 中的坐标转换
一个 View 的 frame 的起点是相当于其所在的 View,即调用 addSubView: 方法的 View。如果要判断两个 View 是否是包含关系,由于两者的起点不同,那么肯定是无法进行比较的。
1 | // rect1和rect2是否有重叠 |
为了统一原点,我们可以使用以下代码:
1 | - (CGPoint)convertPoint:(CGPoint)point toView:(nullable UIView *)view; |
来举两个例子,注意不同情况下 compareView 和 outerView 的参数位置:
1 | CGRect newRect = [self.compareView convertRect:self.innerFrame fromView:self.outerView]; |
得到的就是 innerFrame 在 compareView 中的位置。
UIVisualEffectView 实现高斯模糊
如果想要给一个 view 添加一个高斯模糊的效果,只要在那个 view 上添加一个 UIVisualEffectView 即可。
高斯模糊有三种效果,从浅入深的 style 依次是:
- UIBlurEffectStyleExtraLight
- UIBlurEffectStyleLight
- UIBlurEffectStyleDark
使用的基本实例:
1 | // 要添加模糊的view |
UIInterpolatingMotionEffect 视图运动
UIInterpolatingMotionEffect 可以通过陀螺仪监测手机的倾斜情况。我们可以通过它设置视图响应的运动。
其中有两个属性:minimumRelativeValue 和 maximumRelativeValue。这两个属性控制图像运动的最大范围。
实例:
1 | CGFloat effectOffset = 100.f; |
UISearchController 实现搜索
iOS8 之后,苹果提供了 UISearchController 统一了搜索方式。
首先,需要创建一个用来显示搜索结果的视图 SearchResultsController,它需要实现 UISearchResultsUpdating 协议,在 UISearchController 中的搜索关键字变化的时候,会回调 UISearchResultsUpdating 中的 updateSearchResultsForSearchController: 方法执行数据的筛选搜索。所以,SearchResultController 中还需要两个属性,待搜索的所有数据集合和搜索出的数据集合:
1 | class SearchResultsController: UITableViewController,UISearchResultsUpdating { |
现在要定义一个跳转前的页面。在某个 ViewController 中保存一个 UISearchController 的实例。在初始化 ViewController 的同时,初始化 UISearchController 并拿到 UISearchController 中的 searchBar 的实例,将其添加到 ViewController 的视图中去(为了点击后跳转时,searchBar 的动画效果)。初始化 UISearchController 的时候,将搜索结果展示页 resultsController 传入,并将其赋给 searchResultsUpdater 属性(通过这个属性调用的 updateSearchResultsForSearchController: 方法):
1 | class ViewController: UIViewController { |
其中 scopeButtonTitles 是可选的,在 searchBar 下显示用来进一步筛选的,可以通过 searchController.searchBar.selectedScopeButtonIndex 来获取筛选信息。另外,searchController.searchBar 要确定通过 sizeToFit() 方法确定了大小后,加入到 ViewController 中。
当然,这是最基本的一个流程,还有一些自定义的操作以及一些代理方法