CC_UIAtom實時顯示iOS編寫UI界面 佈局獨立文件

演示動圖
圖片1css

圖片2

下載地址https://github.com/gwh111/ben...html

分析

筆者搜索市面上現有的有名佈局框架,前後研究了AutoLayout、Masonry、Flexbox、FlexLib、ClassyLiveLayout、frame等佈局框架,最後在Classy和ClassyLiveLayout的基礎上改造了一個新佈局框架,主要文件由CC_UIAtom、UIView+ClassyExtend和CC_ClassyExtend組成。可在不從新編譯的狀況下動態修改佈局、顏色、文字等屬性。現支持view、button、label、textview、textfiled這幾個基礎視圖類。
文章前面分析其餘佈局方法,後面介紹新框架實現CC_UIAtom。ios

AutoLayout和Masonry

AutoLayout和Masonry在多視圖構建時性能較差,AutoLayout代碼量最多,Masonry可讀性較強。
AutoLayout的約束是要轉換成frame的,這中間的計算過程下降了性能。git

Masonry是採用鏈式DSL(Domain-specific language)來封裝NSLayoutConstraint
Domain-specific language
https://en.wikipedia.org/wiki...github

Flexbox優化

http://www.cocoachina.com/ios...
FlexBox 的實現 — Yoga
FlexBox是網頁佈局的一套標準
https://css-tricks.com/snippe...
Flex 佈局教程:語法篇
FlexBox 佈局模型
Yoga Tutorial: Using a Cross-Platform Layout Engine
從 Auto Layout 的佈局算法談性能objective-c

FlexLib

FlexLib是一個經過解析xml文件來建立UI佈局的庫。把佈局和代碼分離,很大意義上作到了隔離。減輕編譯壓力、相似安卓的佈局。
https://github.com/zhenglibao...
從代碼中剝離開佈局,仍是須要cmd+r來重啓,對於要登陸進入很深層級頁面的調試麻煩。xml格式須要一些多餘的標籤,寫法容易出錯,可藉助第三方代碼輔助工具來提升佈局速度。算法

Classy

Classy是一個能與UIKit無縫結合stylesheet(樣式)系統。它借鑑CSS的思想,但引入新的語法和命名規則。CC_UIAtom主要依賴了這個庫。
https://classykit.github.io/C...緩存

ClassyLiveLayout

https://github.com/olegam/Cla...
是一位大神封裝的結合Classy、Masonry的佈局框架,支持模擬器上的動態佈局,我很大程度上藉助了他的思路但拋棄了Masonry。
由於沒法在真機使用啓用Live Reload,僅模擬器支持Live Reloadapp

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/b...
https://www.jianshu.com/p/35d...
有些人以爲這個方法多餘,不是很好用。
咱們使用了兩個方法 objc_getAssociatedObject 以及 objc_setAssociatedObject 來模擬『屬性』的存取方法,而使用關聯對象模擬實例變量。在CC_UIAtom主要使用了這個技能。

CC_UIAtom介紹

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");
    }];
}];

普通建立和用CC_UIAtom建立實例對比

//普通建立
UILabel *l=[[UILabel alloc]initWithFrame:CGRectMake(250, 100, 100, 40)];
l.backgroundColor=[UIColor yellowColor];
l.text=@"普通label";
[self.view addSubview:l];
    
//CC_UIAtom建立
[CC_UIAtom initAt:self.view name:@"MainVC_i_figure1" class:[UIImageView class] finishBlock:^(UIImageView *atom) {

}];
    
[CC_UIAtom initAt:self.view name:@"MainVC_b_box1" class:[CC_Button class] finishBlock:^(CC_Button *atom) {
    //更多代碼
    [atom setBackgroundColor:[UIColor brownColor]];
    [atom addTappedOnceDelay:.1 withBlock:^(UIButton *button) {
            
        [self requestxxx1];
    }];
}];

模塊組成

UIView+ClassyExtend

是一個擴展文件,在裏面實現了動態讀取更新的cas文件,並修改控件屬性。參考了ClassyLiveLayout。
對於修改後的cas文件,由於在啓動時有監聽,會經過Live Reload通知,而後經過objc_setAssociatedObject和objc_getAssociatedObject更新屬性,實現不編譯修改。更新完會實現回調,爲了處理動態修改,好比頁面接收到數據後才調整UI,就須要在回調裏修改佈局,不然會被覆蓋。

CC_ClassyExtend

一個解析佈局的文件,主要任務是解析佈局爲了給真機使用。
建議全部cas文件放在二級目錄內,我的喜愛按控件分文件夾。

總結一下整個流程

啓動後讀取stylesheet.cas的文件,解析裏面引用的文件路徑,讀取這些文件中的控件屬性,把它們合併,建立一個配置文件放入緩存。在實際使用中,不管真機和模擬器,都會讀取配置文件中的配置並構建,模擬器是直接讀取文件,真機是讀取生成的文件。
建議stylesheet.cas只添加cas文件路徑,將全部佈局寫到單獨模塊目錄下。

用pods安裝

platform :ios, '8.0'
inhibit_all_warnings!

target 'xxx' do
  pod 'bench_ios'
end

Classy合併的警告

CASUtilities.h
this function declaration is not a prototype
解決:添加void
UIColor+CASAdditions.m:18:37: warning: format specifies type 'unsigned int ' but the argument has type 'NSUInteger ' (aka 'unsigned long *')
解決使用int "%d", &result
UIColor+CASAdditions.m:85:53: warning: values of type 'NSUInteger' should not be used as format arguments; add an explicit cast to 'unsigned long' instead
解決[hex stringByAppendingFormat:@"%02lx", (unsigned long)(CGColorGetAlpha(self.CGColor) * 255.0f)]
Classy.h:25:9: warning: non-portable path to file '"UIToolbar+CASAdditions.h」'
解決:把名字複製一遍,刪掉import,從新寫一遍

待完善問題:
一、在block外也能夠調整佈局(已添加)
二、添加控件之間的約束,計劃仿造安卓添加一個toRightOf(已添加)

添加約束屬性列表

/**
 *  控件寬
 *  控件高
 *  控件寬和指定控件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;
相關文章
相關標籤/搜索