前言
一个控件从外在特征来说,主要是封装这几点:
交互方式
显示样式
数据使用
对外在特征的封装,能让我们在多种环境下达到 PM 对产品的要求,并且提到代码复用率,使维护工作保持在一个相对较小的范围内;而一个好的控件除了有对外一致的体验之外,还有其内在特征:
灵活性
低耦合
易拓展
易维护
通常特征之间需要做一些取舍,比如灵活性与耦合度,有时候接口越多越能适应各种环境,但是接口越少对外产生的依赖就越少,维护起来也更容易。通常一些前期看起来还不错的代码,往往也会随着时间加深慢慢“成长”,功能的增加也会带来新的接口,很不自觉地就加深了耦合度,在开发中时不时地进行一些重构工作很有必要。总之,尽量减少接口的数量,但有足够的定制空间,可以在一开始把接口全部隐藏起来,再根据实际需要慢慢放开。
自定义控件在 iOS 项目里很常见,通常页面之间入口很多,而且使用场景极有可能大不相同,比如一个 UIView 既可以以代码初始化,也可以以 xib 的形式初始化,而我们是需要保证这两种操作都能产生同样的行为。本文将会讨论到以下几点:
选择正确的初始化方式
调整布局的时机
正确的处理 touches 方法
drawRectCALayer 与动画
UIControl 与 UIButton
更友好的支持 xib
不规则图形和事件触发范围(事件链的简单介绍以及处理)
合理使用 KVO
如果这些问题你一看就懂的话就不用继续往下看了。
设计方针
选择正确的初始化方式
UIView 的首要问题就是既能从代码中初始化,也能从 xib 中初始化,两者有何不同? UIView 是支持 NSCoding 协议的,当在 xib 或 storyboard 里存在一个 UIView 的时候,其实是将 UIView 序列化到文件里(xib 和 storyboard 都是以 XML 格式来保存的),加载的时候反序列化出来,所以:
当从代码实例化 UIView 的时候,initWithFrame 会执行;
当从文件加载 UIView 的时候,initWithCoder 会执行。
如何封装一组控件?
比如拿UIScrollView 来讲,实现一个很容易,但是由于我会在项目中多次用到UIScrollView ,所以,需要把它再封装一次,方便后面调用。
如果在多个页面有相同的代码或逻辑这时你就可以考虑分离公共代码对它们进行封装。你这里的uiscrollview 要看你的需求,如果是只针对uiscrollview的某些公共部分的封装,你可以直接继承uiscrollview进行扩展。如果是uiscrollivew只是作为控件封装的一部分存在,而且还有一些与uiscrollview无关的逻辑,这时你就需要继承更顶层的uiview来实现你的自定义控件。
基本流程如下:
代码实现:
PWLScrollView.h
PWLScrollView.m
RootViewController.m