https://github.com/gwh111/bench_ios
更新待上传。。完善些细节后近日更新
platform :ios, '8.0'
#use_frameworks!个别需要用到它,比如reactiveCocoa
inhibit_all_warnings!
target 'xxx' do
pod 'bench_ios'
end
笔者搜索市面上现有的有名布局框架,先后研究了AutoLayout、Masonry、Flexbox、FlexLib、ClassyLiveLayout、frame等布局框架,最后在Classy和ClassyLiveLayout的基础上改造了一个新布局框架,主要文件由CC_UIAtom、UIView+ClassyExtend和CC_ClassyExtend组成。可在不重新编译的情况下动态修改布局、颜色、文字等属性。现支持view、button、label、textview、textfiled这几个基础视图类。
文章前面分析其他布局方法,后面介绍新框架实现CC_UIAtom。
AutoLayout和Masonry在多视图构建时性能较差,AutoLayout代码量最多,Masonry可读性较强。
AutoLayout的约束是要转换成frame的,这中间的计算过程降低了性能。
Masonry是采用链式DSL(Domain-specific language)来封装NSLayoutConstraint
Domain-specific language
https://en.wikipedia.org/wiki/Domain-specific_language
Flexbox优化
http://www.cocoachina.com/ios/20170314/18878.html
FlexBox 的实现 — Yoga
FlexBox是网页布局的一套标准
https://css-tricks.com/snippets/css/a-guide-to-flexbox/
Yoga Tutorial: Using a Cross-Platform Layout Engine
FlexLib
FlexLib是一个通过解析xml文件来创建UI布局的库。把布局和代码分离,很大意义上做到了隔离。减轻编译压力、类似安卓的布局。
https://github.com/zhenglibao/FlexLib
从代码中剥离开布局,还是需要cmd+r来重启,对于要登录进入很深层级页面的调试麻烦。xml格式需要一些多余的标签,写法容易出错,可借助第三方代码辅助工具来提高布局速度。
Classy
Classy是一个能与UIKit无缝结合stylesheet(样式)系统。它借鉴CSS的思想,但引入新的语法和命名规则。CC_UIAtom主要依赖了这个库。
https://classykit.github.io/Classy/getting-started/
ClassyLiveLayout
https://github.com/olegam/ClassyLiveLayout
是一位大神封装的结合Classy、Masonry的布局框架,支持模拟器上的动态布局,我很大程度上借助了他的思路但抛弃了Masonry。
因为无法在真机使用启用Live Reload,模拟器支持Live Reload
Live Reload
Live reload can dramatically speed up your development time, with live reload enabled you can instantaneously see your stylesheet changes. Without having to rebuild and navigate back to the same spot within your app.
For more details about these features and more visit the docs or the wiki.
对于该项目中用到的主要技术
关联对象 AssociatedObject
http://blog.leichunfeng.com/blog/2015/06/26/objective-c-associated-objects-implementation-principle/
https://www.jianshu.com/p/35df1ba1f107
有些人觉得这个方法多余,很装x但不好用。
我们使用了两个方法 objc_getAssociatedObject 以及 objc_setAssociatedObject 来模拟『属性』的存取方法,而使用关联对象模拟实例变量。在CC_UIAtom中更新的通知主要使用了这个技能。
平台的判断
#if !TARGET_IPHONE_SIMULATOR
#else
#endif
CC_UIAtom、UIView+ClassyExtend和CC_ClassyExtend
CC_UIAtom中封装了控件的类别
/**
* 控件的类别
*/
typedef enum : NSUInteger {
CCView,
CCLabel,
CCButton,
CCTextField,
CCTextView,
} CCAtomType;
+ (id)initAt:(UIView *)view name:(NSString *)name type:(CCAtomType)type finishBlock:(void (^)(id atom))block;
使用了一个方法来构建一个布局控件,通过判断控件类型,初始化一个对应控件。
之后在cas文件中配置对应参数,注意cas文件中控件名和这里初始化的名字要对应不能重复。
通过objc_setAssociatedObject将block传递给UIView,在更新布局后回调出去,才可对布局做后期修改更新(如接口接收到数据后更新布局)。
CC_Button *button=[CC_UIAtom initAt:self.view name:@"MainVC_b_box1" type:CCButton finishBlock:^(CC_Button *atom) {
[atom setBackgroundColor:[UIColor brownColor]];
[atom addTappedOnceDelay:.1 withBlock:^(UIButton *button) {
CCLOG(@"tapped");
}];
}];
UIView+ClassyExtend
是一个扩展文件,在里面实现了动态读取更新的cas文件,并修改控件属性。参考了ClassyLiveLayout。
对于修改后的cas文件,因为在启动时有监听,会通过Live Reload通知,然后通过objc_setAssociatedObject和objc_getAssociatedObject更新属性,实现不编译修改。更新完会实现回调,为了处理动态修改,比如页面接收到数据后才调整UI,就需要在回调里修改布局,否则会被覆盖。
CC_ClassyExtend
一个解析布局的文件,主要任务是解析布局为了给真机使用。
建议所有cas文件放在二级目录内,个人喜好按控件分文件夹。
总结一下整个流程
启动后读取stylesheet.cas的文件,解析里面引用的文件路径,读取这些文件中的控件属性,把它们合并,创建一个配置文件放入缓存。在实际使用中,无论真机和模拟器,都会读取配置文件中的配置并构建,模拟器是直接读取文件,真机是读取生成的文件。
建议stylesheet.cas只添加cas文件路径,将所有布局写到单独模块目录下。
https://blog.csdn.net/gwh111/article/details/81168939
属性列表
/**
* 控件宽
* 控件高
* 控件宽和指定控件id的宽相等
* 控件高和指定控件id的高相等
* 控件宽和父视图宽相等 如比父视图少5 填“-5” 大10 填“10” 相等 填“0”
* 控件高和父视图高相等
* 控件宽和屏幕宽相等 如比父视图少5 填“-5” 大10 填“10” 相等 填“0”
* 控件高和屏幕高相等
*/
@property(nonatomic, assign) CGFloat cas_width;
@property(nonatomic, assign) CGFloat cas_height;
@property(nonatomic, retain) NSString *cas_widthSameAs;
@property(nonatomic, retain) NSString *cas_heightSameAs;
@property(nonatomic, retain) NSString *cas_widthSameAsParent;
@property(nonatomic, retain) NSString *cas_heightSameAsParent;
@property(nonatomic, retain) NSString *cas_widthSameAsScreen;
@property(nonatomic, retain) NSString *cas_heightSameAsScreen;
/**
* 上偏移的值
* 下偏移的值
* 左偏移的值
* 右偏移的值
*/
@property(nonatomic, assign) CGFloat cas_marginTop;
@property(nonatomic, assign) CGFloat cas_marginBottom;
@property(nonatomic, assign) CGFloat cas_marginLeft;
@property(nonatomic, assign) CGFloat cas_marginRight;
/**
* 背景色
* 背景图
* 文字
* 字体颜色
* 字体大小
*/
@property(nonatomic, retain) NSString *cas_backgroundColor;
@property(nonatomic, retain) NSString *cas_backgroundImage;
@property(nonatomic, retain) NSString *cas_text;
@property(nonatomic, retain) NSString *cas_textColor;
@property(nonatomic, assign) int cas_font;
/**
* 控件ID为自定义的控件名
* 将该控件的底部置于给定ID的控件之上;
* 将该控件的底部置于给定ID的控件之下;
* 将该控件的右边缘与给定ID的控件左边缘对齐;
* 将该控件的左边缘与给定ID的控件右边缘对齐;
*/
@property(nonatomic, retain) NSString *cas_above;
@property(nonatomic, retain) NSString *cas_below;
@property(nonatomic, retain) NSString *cas_toRightOf;
@property(nonatomic, retain) NSString *cas_toLeftOf;
/**
* 设为@“1”即可
* 将该控件的顶部边缘与给定ID的顶部边缘对齐;
* 将该控件的底部边缘与给定ID的底部边缘对齐;
* 将该控件的左边缘与给定ID的左边缘对齐;
* 将该控件的右边缘与给定ID的右边缘对齐;
*/
@property(nonatomic, retain) NSString *cas_alignTop;
@property(nonatomic, retain) NSString *cas_alignBottom;
@property(nonatomic, retain) NSString *cas_alignLeft;
@property(nonatomic, retain) NSString *cas_alignRight;
/**
* 设为@“1”即可
* 如果为true,将该控件的顶部与其父控件的顶部对齐;
* 如果为true,将该控件的底部与其父控件的底部对齐;
* 如果为true,将该控件的左部与其父控件的左部对齐;
* 如果为true,将该控件的右部与其父控件的右部对齐;
*/
@property(nonatomic, retain) NSString *cas_alignParentTop;
@property(nonatomic, retain) NSString *cas_alignParentBottom;
@property(nonatomic, retain) NSString *cas_alignParentLeft;
@property(nonatomic, retain) NSString *cas_alignParentRight;