2018·第15 期

从这期开始,小集团队调整一下文章发布姿势,将「 iOS知识小集 」合集放到周日来发布,以留出其它时间来发技术类文章。

WWDC 2018 我们会和 、 团队一起合作,敬请期待。

本期知识小集的内容信息量有点大,主要内容包括:

如何定制一个 UIView 类型控件的出入动画

UIView 的事件透传

iOS 如何调试 WebView (二)

一个结构较为合理的下载模块该怎么设计

再谈 iOS 输入框的字数统计/最大长度限制

如何定制一个 UIView 类型控件的出入动画

作者: halohily

在 iOS 开发中,自定义的弹层组件非常常见,比如分享框、自定义的 actionSheet 组件等。有的场景下,会选择使用 UIViewController 类型来实现,这时定制这个视图的出现、隐藏动画非常方便。然而,有时候需要选择轻量级的 UIView 类型来实现。这时该怎么定制它的出现、隐藏动画呢?这里提供一个思路:

使用 UIView 的 和 这组方法,它们会在 作为subView 被添加到其他 UIView 中时调用。这里需要注意,自身调用 方法时,同样会触发这组方法,只不过这时的参数会是一个 nil。

提供一个例子来说明:一个选择 UIView 类型实现的自定义 actionSheet 的出入动画,交互基本和微信一致。

#pragma mark - show & dismiss

- (void)didMoveToSuperview {

if(self.superview) {

[UIViewanimateWithDuration:0.35delay:usingSpringWithDamping:0.9initialSpringVelocity:10options:UIViewAnimationOptionCurveEaseInanimations:^{

_backgroundControl.alpha =1;

self.actionSheetTable.frame =CGRectMake(, SCREEN_HEIGHT - _sheetHeight, SCREEN_WIDTH, _sheetHeight);

} completion:^(BOOLfinished) {

[superdidMoveToSuperview];

}];

}

}

- (void)hideSelf {

[UIViewanimateWithDuration:0.35delay:usingSpringWithDamping:0.9initialSpringVelocity:10options:UIViewAnimationOptionCurveEaseInanimations:^{

_backgroundControl.alpha =;

self.actionSheetTable.frame =CGRectMake(, SCREEN_HEIGHT, SCREEN_WIDTH, _sheetHeight);

} completion:^(BOOLfinished) {

[selfremoveFromSuperview];

}];

}

UIView 的事件透传

作者: Vong_HUST

通常我们会遇到这种需求,一个视图除了需要响应子视图的点击事件,其它空白地方希望能将点击事件透传到,比如自定义了一个“导航栏”,除了左右两边按钮,希望其它部分点击能够透传到底下的视图。这个时候我们可以通过复写 方法,具体实现如下。

@implementationPassthroughView

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {

if(self.hidden ||self.alpha

return[superhitTest:point withEvent:event];

}

UIView*targetView =nil;

for(UIView*subviewin[[selfsubviews] reverseObjectEnumerator]) {

if((targetView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event])) {

break;

}

}

returntargetView;

}

@end

以上代码即可实现,只响应子视图的事件,而非子视图区域部分的交互事件则透传到响应链中的下一个响应者。

如果你有其它更好方式,也可以分享出来,一起交流下。

iOS 如何调试 WebView (二)

作者: Lefe_x

上次的小集中,我主要讨论了如何调试 WebView ,小集发出后 提供了另一种方法来调试 WebView。我觉得有必要再扩展一下,原话是这样的:

真说方便还是植入一个 webview console 在 debug 环境,可以在黑盒下不连电脑不连 safari 调 dom,调 js,另外在开发期间 Xcode 断点 run 的时候,js hook console.log console.alert,接管 window.onerror 全都改 bridge NSLog 输出,也会方便点。

短短几句话,信息量很大,私下向味精学习了下,这里总结一下。写完这个小集特意让味精看了下,觉得有必要再补充下第二种调试技巧,但中途踩了几个坑,一直到23:30左右才搞定。

第一,把 WebView 用来调试的 log、alert、error 显示到 NA ,在调试时会方便不少。做 WebView 与端交互的时候,主要用 来给端发消息,也就是说 WebView 想给端发消息的时候直接调用这个方法即可,端会通过 的代理方法来接收消息,而此时端根据和 WebView 约定的规则进行通信即可。

- (void)userContentController:(WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message

而添加调试信息,无非就是给 WebView 添加了 log、alert、error 这些消息的 bridge,这样当 WebView 给端发送消息后,端根据和 WebView 约定的规则解析 log、alert、error 为端对应的事件,比如 log 直接调用端的 ,alert 调用端的 。

第二,黑盒下调试 WebView,无需连接电脑和 safari 即可调试 DOM,这个可以参考小程序的 vConsole 或者 eruda 。可以直接在 WebView 中接入,或者在端中接入。这里以在端中接入 eruda 为例,这里踩到几个坑:

1.有些页面显示不出来,估计是故意屏蔽掉的,味精特意使用 JSBox 试了下其它页面,发现百度等都不可以显示调试按钮,而掘金是可以的;

2.使用本地的页面也显示不出来,这是 webview 跨域安全方面的考虑,file 协议下会禁止 js css html 以部分 file,部分网络的方式加载。

下面这段代码直接在 webview 加载完成后执行即可。

NSString*js =@"(function() )();";

[self.webView evaluateJavaScript:js completionHandler:nil];

一个结构较为合理的下载模块该怎么设计

作者: halohily

“下载”作为一个需要本地结构化、持久化存储的场景,使用数据库是比较自然的选择。所以,我们首先拆分出一个数据库模块,用来存储下载记录。主要字段为下载任务的信息,如 url、文件大小、时间戳等,以及最重要的文件本地存储路径。这一层可以在接口设计上认真思虑,比如仅涉及当前业务逻辑,而不涉及具体的数据库操作,相当于是较 FMDB 等数据库组件来说更高层的抽象。后期需要更换底层数据库引擎时,本层封装无需改动,是比较理想的实现。

数据库是用来存储下载记录的,那么所下载的具体文件呢?自然就需要一个文件管理模块,在这个模块里,负责根据文件 url 生成本地的存储路径,以及进行文件校验、存储、移除等操作。

所要下载的文件,我们可以按体积、类型等进行区分。对于网络请求的结果这类简短内容,我抽象出了一个缓存管理器,用来完成网络请求、图片等内容的缓存。网络请求的 JSON 格式结果,可以选择 、 等缓存框架。而图片的缓存,则可以选择专注图片缓存的 、 等框架。

对于体积较大的文件,自然需要一个专注大文件下载的模块。这个模块不关注具体的文件类型,不关注具体的业务场景,它只需要文件 url 、文件管理模块生成的本地目标路径,完成下载任务即可。

在以上通用模块的基础上,有一个业务层的封装,它负责根据提交的下载任务,协调调用各基础组件。举个例子,一个下载任务包括一个视频文件、一个网络请求结果、三张图片。本模块在收到任务后,首先解析出以上的任务具体结构。使用文件管理模块,根据视频文件 url 生成本地存储目标路径,调用大文件下载器完成下载,此为一个子任务。对于网络请求结果,调用缓存模块,进行缓存,此为一个子任务。对于三张图片,使用图片缓存器完成缓存,此为一个子任务。三个子任务均完成,使用数据库模块,对下载记录、媒体文件记录等进行存储。除此之外,本模块还负责对外提供下载中任务、已下载任务等数据。

再谈 iOS 输入框的字数统计/最大长度限制

作者: KANGZUBIN

前两周我们发了一个小集「iOS 自带九宫格拼音键盘与 Emoji 表情之间的坑」,介绍了如何解决由于输入框限制 Emoji 表情的输入导致中文拼音也无法输入的问题。

后面我们又有了新需求:对输入框已输入的文本字数进行实时统计,并在界面上显示剩余字数,且不能让所输入的文本超过最大限制长度。但这个简单的功能仍然有不少小坑。

在上一个小集中,我们讲到,对于 iOS 系统自带的键盘,有时候它在输入框中填入的是占位字符(已被高亮选中起来),等用户选中键盘上的候选词时,再替换为真正输入的字符,如下:

这会带来一个问题:比如输入框限定最多只能输入 10 位,当已经输入 9 个汉字的时候,使用系统拼音键盘则第 10 个字的拼音就打不了(因为剩余的 1 位无法输入完整的拼音)。

怎么办呢?上面提到,输入框中的拼音会被高亮选中起来,所以我们可以根据 的 属性判断是否存在高亮字符,如果有则不进行字数统计和字符串截断操作。我们通过监听 事件来对输入框内容的变化进行相应处理,如下:

[self.textField addTarget:selfaction:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];

- (void)textFieldDidChange:(UITextField*)textField {

// 判断是否存在高亮字符,如果有,则不进行字数统计和字符串截断

UITextRange*selectedRange = textField.markedTextRange;

UITextPosition*position = [textField positionFromPosition:selectedRange.start offset:];

if(position) {

return;

}

// maxWowdLimit 为 0,不限制字数

if(self.maxWowdLimit ==) {

return;

}

// 判断是否超过最大字数限制,如果超过就截断

if(textField.text.length >self.maxWowdLimit) {

textField.text = [textField.text substringToIndex:self.maxWowdLimit];

}

// 剩余字数显示 UI 更新

}

对于 的处理也是类似的。

另外,对于“字数”的定义是很多种理解:在 Objective-C 中字符串 的长度 ,对于一个中文汉字和一个英文字母都是 1;但如果我们要按字节来统计和限制,同一字符在不同编码下所占的字节数也是不同的;另外有时我们要统计的是所输入文本的单词个数,而不是字符串的长度,所以我们需要根据不同的使用场景进行分析。

关注我们

欢迎关注我们的公众号:iOS-Tips,也欢迎加入我们的群组讨论问题:

「 知识小集 · 2号群 」人数快满500,可以先加微信 或,请注明;目前1、2号群都已经很活跃;2号群满群后,我们会增开3号群;

「 知识小集 · Flutter 自习室 」人数到 200,可以先加微信 或者,请注明Flutter 入群

「 知识小集 · 前端修行室 」,可以先加微信 或,请注明

「 知识小集 · PWA 实验室 」,可以先加微信 或,请注明

「 知识小集 · 小程序交流群 」,**可以先加微信 **,请注明

另外上面技术群群规比较严,主要以讨论技术为主,发广告等行为都一律踢出群。某天「 知识小集 · 前端修行室 」进来个妹子,然后群就炸了,控制不住大家的情绪,所以专门开了个,这个群暂时不对外开放,可以加入技术群后再拉群。

周五的时候我们说要做一次送书活动,征集了几本书,在这里发个投票,在 WWDC 2018 的相关工作完成之后,我们会发起活动。投票里的书会根据价格、获取难易度来确定最后的送哪本书以及数量(如《深入解析 Mac OS X & iOS 操作系统》这本书比较难买)

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180603G08TLX00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券