很多不常用的简单的 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
中。
当然,这是最基本的一个流程,还有一些自定义的操作以及一些代理方法