内容简介:对于iOS开发来说,写一个自定义view,或者恰当地使用tableview基本上可以算的上是“行活”。但是看过一些同学写的自定义控件后,有时感觉似乎写的不够好,虽然可以正常工作,但是在可拓展性、易用性、以及稳定性上都有所欠缺。所以我打算写一个系列,就叫做如何写好xxx,就总结下我认为的好的写法应该是什么样的,这篇便是这系列的第一篇。当然受视野和水平所限,文章中提到的一些东西并不一定是最优解,非常欢迎大家提出不同的意见,讨论后共同成长!使用方式多样
前言
对于iOS开发来说,写一个自定义view,或者恰当地使用tableview基本上可以算的上是“行活”。但是看过一些同学写的自定义控件后,有时感觉似乎写的不够好,虽然可以正常工作,但是在可拓展性、易用性、以及稳定性上都有所欠缺。所以我打算写一个系列,就叫做如何写好xxx,就总结下我认为的好的写法应该是什么样的,这篇便是这系列的第一篇。
当然受视野和水平所限,文章中提到的一些东西并不一定是最优解,非常欢迎大家提出不同的意见,讨论后共同成长!
目标
使用方式多样
-
纯代码中使用
-
Xib/storyboard中使用
使用的易用性
-
尽量简单的接口设计
-
尽量少的暴露实现
-
对异常情况的处理
实现
初始化方法
这里我们大可借鉴一下UIKit中系统的UI组件是如何设计自己的初始化方法的。
UIKit中初始化方法大概分为两类,
1.继承自父类的Designated initializer
-
initWithFrame
-
initWithCoder(不是所有的UI类都继承UIView,例如继承NSObject的UIBarItem,这些就没有initWithFrame方法)
2.Convenience Initializer,例如UITabBarItem中的
- (instancetype)initWithTitle:(nullable NSString *)title image:(nullable UIImage *)image tag:(NSInteger)tag; - (instancetype)initWithTitle:(nullable NSString *)title image:(nullable UIImage *)image selectedImage:(nullable UIImage *)selectedImage NS_AVAILABLE_IOS(7_0); - (instancetype)initWithTabBarSystemItem:(UITabBarSystemItem)systemItem tag:(NSInteger)tag;
等方法
首先,我们要搞清楚什么是Designated initializer和Convenience Initializer。
-
Designated initializer,初始化类必须有的属性
-
Convenience Initializer,提供便利的初始化方法,根据需要为某些属性提供默认值,方法内部实现最终还是会调用Designated initializer;
其次,为什么UIView的子类都会有两个Designated initializer呢?这里就是我们之前提到的,View的两种使用方法,Xib/storyboard,和纯代码。
实现Designated initializer
为了既能满足纯代码的方式,又能使用Xib的方式,我们需要实现CustomView的两个Designated initializer
而且在swift中,initWithCoder已经被标记为required,所以必须要实现啦
在实现这两个方式时,主要做的就是添加子view,以及提供默认值
提供Convenience Initializer
例如UIImageView,他就提供了initWithImage 这个Convenience Initializer。
使用Convenience Initializer的好处也是显而易见的,能让类的使用者很清楚的知道我应该如何正确的初始化这个类。而且会对必需的属性提供默认值,既能极大的避免了调用者只调用init,导致该实例并不能正常工作,又能在很多属性时,提供一个简单的初始化方法。
内部子view布局的实现
frame or autoLayout?
如果使用frame,我们需要保证custom view自己的size发生变化的时候,subviews能够自动变化,而不是保持原有的frame。(autoresizingMask,autoresizesSubviews)
如果使用autoLayout,就不存在上面的问题,唯一一个需要考虑的问题便是性能了。通过WWDC也可以知道,虽然苹果对于autolayout一再优化,仍然在多视图情境下,性能远不如frame
我的观点 :如果页面层级不复杂,性能差别也不大,我还是倾向使用AutoLayout,毕竟算frame也是比较麻烦,而且代码可读性也要比AutoLayout差很多
构建视图
这时需要解释几个很重要的方法,以及什么时候需要使用
-(void)drawRect:(CGRect)rect
使用场景:需要使用Core Graphics或者UIKit绘制页面时,如果是使用已有UI控件addsubview组合自己的view,则不需要重写这个方法
被调用时机:view首次显示的时候,或者某个事件导致了view需要更新,不要直接手动调用。如果需要重绘,调用 setNeedsDisplay 或者 setNeedsDisplayInRect:
参数说明:view需要更新的范围,如果是连续的绘制,那么rect可能只是view的一部分
-(void)layoutSubviews
使用场景:只有当autoresizing和约束不能满足你的需求时,才重写layoutSubviews来提供更精确的布局
被调用时机:不要直接手动调用,调用setNeedsLayout来更新约束,如果需要立即更新约束,那么调用 layoutIfNeeded
-(void)updateConstraints;
使用场景:为了优化约束的变化,需要提早改变约束,或者产生大量冗余修改时。
被调用时机:不要直接手动调用,在view需要修改约束的时候,调用setNeedsUpdateConstraints
注意事项:
在实现的最后,调用[Super updateConstraints]
不要在方法实现中调用setNeedsUpdateConstraints,会产生循环
接口的设计
接口设计尽量遵循Effective Objective-C 2.0中的建议,比如必须暴露的属性尽量为readonly,内部实现的私有方法不必暴露出去。在设计接口的时候,时刻要想着,这个方法,这个属性真的有必要让别人知道吗?这个方法真正的目的是什么?总之,尽量遵循Keep It Simple, Stupid就对了。
线程管理
所有UI的操作都应该在主线程进行,这需要我们在涉及到UI变动的方法中,确保是主线程,而不依赖使用者
一个简单的例子
TCZoomingImageView是基于UIScrollView和UIImageView做一个可缩放的View,实现非常的简单,仅作为一个简单的例子,抛砖引玉。
GitHub地址: TCZoomingImageView
作者:王天池
链接:https://juejin.im/post/5bb1e7df5188255c9c754182
以上所述就是小编给大家介绍的《如何写好一个自定义View》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Android 自定义 View (04自定义属性)
- Vue自定义组件(简单实现一个自定义组件)
- Android 自定义View:深入理解自定义属性(七)
- Qt编写自定义控件20-自定义饼图 原 荐
- SpringBoot2 | SpringBoot自定义AutoConfiguration | SpringBoot自定义starter(五)
- 『互联网架构』软件架构-springboot自定义视图和自定义Starter(90)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
社交的本质:扎克伯格的商业秘密
兰迪•扎克伯格 / 谢天 / 中信出版集团股份有限公司 / 2016-6-1 / CNY 45.00
从发表个人观点到找工作,从交朋友到找伴侣,社会化媒体的广泛应用、互联技术的高速发展已经改变了我们生活的各个领域。 Facebook早期成员之一,兰迪·扎克伯格阐述了社交的本质,并首次披露Facebook的商业策略。她以社交媒体实践者的视角,分享了自己在Facebook负责营销的从业经历与成长故事,以及对互联网和社会未来变化趋势的思考,并给组织和个人提出了解决方案。一起来看看 《社交的本质:扎克伯格的商业秘密》 这本书的介绍吧!