NerdyUI
github: https://github.com/nerdycat/NerdyUI
Cupcake (Swift 版本)
github: https://github.com/nerdycat/Cupcake
相關(guān)文章:
NerdyUI 使用小技巧
序言
眾所周知,UI在一個(gè)App中所占的比重是很大的,如果能快速的布局UI,就能大大的提高App的整體開(kāi)發(fā)效率,NerdyUI正是基于這個(gè)理由創(chuàng)建的。
NerdyUI使用非常緊湊的鏈?zhǔn)秸Z(yǔ)法,提供一些常用但系統(tǒng)控件又缺失的功能,更為簡(jiǎn)便的約束創(chuàng)建方式和更好理解的布局系統(tǒng),勢(shì)必能大大減少您的代碼量和開(kāi)發(fā)時(shí)間。
快速創(chuàng)建 NSString, UIFont, UIColor, UIImage 和常用的 structs
你可以用 Str()
來(lái)轉(zhuǎn)換大部分類型到NSString。同理,你可以用 Log()
來(lái)打印大部分的變量。
Str(100); //@"100"
Str(3.14); //@"3.14"
Str(@0.618); //@"0.618"
Str(view.frame); //@"{{0, 0}, {100, 100}}"
Str(view.center); //@"{50, 50}"
Str(_cmd); //@"viewDidLoad"
Str(NSString.class); //@"NSString"
Str("C-String"); //@"C-String"
Str(@"1 + 1 = %d", 1 + 1); //@"1 + 1 = 2
Log(100);
Log(3.14);
Log(@0.618);
Log(view.frame);
...
Log(@"1 + 1 = %d", 1 + 1);
//拼接字符串
@"1".a(@"2").a(3).a(nil).a(4.0f).a(@5).a(@"%d", 6); //@"123456"
你可以用 AttStr()
來(lái)創(chuàng)建NSAttributedString。
AttStr(@"hello, 101").match(@"[0-9]+").underline; //給101加下劃線
AttStr(@"A smile ", Img(@"smile"), @" !!"); //帶圖片的NSAttributedString
你可以用 Fnt()
來(lái)創(chuàng)建UIFont。
Fnt(15); //[UIFont systemFontOfSize:15]
Fnt(@15); //[UIFont boldSystemFontOfSize:15]
Fnt(@"body"); //UIFontTextStyleBody
Fnt(@"Helvetica,15"); //helvetica font with size 15
你可以用 Color()
來(lái)創(chuàng)建UIColor。
Color(@"red"); //[UIColor redColor]
Color(@"0,0,255"); //RGB color
Color(@"#0000FF"); //Hex color
Color(@"random"); //random color
//以上這些還可以有一個(gè)可選的alpha參數(shù)
Color(@"red,0.5"); //red color with alpha 0.5
Color(@"0,0,255,0.8"); //blue color with alpha 0.8
...
Color(Img(@"pattern")); //pattern image color
你可以用 Img()
來(lái)創(chuàng)建UIImage。
Img(@"imageName"); //[UIImage imageNamed:@"imageName"]
Img(@"#imageName"); //加上#號(hào)會(huì)返回一個(gè)可拉伸的圖片,拉伸位置在圖片中心
Img(@"red"); //返回一個(gè) 1x1 大小的紅色圖片,可用在某些需要純色圖片的地方
你可以用 XY()
, WH()
, XYWH()
, Range()
, Insets()
來(lái)創(chuàng)建CGPoint, CGSize, CGRect, NSRange, UIEdgeInsets。XYWH()
和 Insets()
支持多種創(chuàng)建方式。
CGPoint p = XY(20, 20);
CGSize s = WH(50, 50);
CGRect f1 = XYWH(20, 20, 50, 50);
CGRect f2 = XYWH(f1.origin, f1.size);
CGRect f3 = XYWH(f2.origin, 50, 50);
CGRect f4 = XYWH(20, 20, f3.size);
NSRange r = Range(10, 20);
UIEdgeInsets i1 = Insets(10); //{10, 10, 10, 10}
UIEdgeInsets i2 = Insets(10, 20); //{10, 20, 10, 20}
UIEdgeInsets i3 = Insets(10, 20, 30); //{10, 20, 30, 20}
UIEdgeInsets i4 = Insets(10, 20, 30, 40); //{10, 20, 30, 40}
使用這些宏可以簡(jiǎn)化一些常見(jiàn)類型的創(chuàng)建過(guò)程,更重要的是你可以用同樣的方式來(lái)設(shè)置視圖的屬性值,稍后你就會(huì)明白這是什么意思。
快速訪問(wèn)frame屬性和屏幕大小
someView.x = 10;
someView.y = someView.x;
someView.xy = XY(10, 10);
someView.w = 50; //width
someView.h = someView.w; //height
someView.wh = WH(50, 50);
someView.frame = XYWH(10, 10, 50, 50);
someView.cx = 25;
someView.cy = someView.cx;
someView.center = XY(25, 25);
someView.maxX = 60;
someView.maxY = someView.maxX;
someView.maxXY = XY(60, 60);
//Screen只是 [UIScreen mainScreen] 的宏定義
someView.wh = WH(Screen.width, Screen.height);
我猜大部分人都有類似的擴(kuò)展吧
快速的創(chuàng)建UI控件
NerdyUI 使用鏈?zhǔn)秸Z(yǔ)法來(lái)快速的創(chuàng)建和設(shè)置 UI 控件。
UIView *view1 = View.xywh(20, 30, 50, 50).bgColor(@"red").opacity(0.7).border(3, @"3d3d3d");
UIView *view2 = View.xy(80, 30).wh(view1.wh).bgColor(@"blue,0.7").borderRadius(25).shadow(0.8).onClick(^{
Log(@"view2");
});

UIImageView *moose = ImageView.img(@"moose").x(20).y(100).shadow(0.6, 2, -3, -1);
UILabel *quiz = Label.str(@"%d+%d=?", 1, 1).fnt(@17).color(@"66,66,66").fitSize.x(moose.maxX + 10).cy(moose.cy);

//如果后續(xù)不需要再訪問(wèn) title 的屬性,定義為 id 可以減少一些代碼量
id title = AttStr(@"TAP ME").fnt(15).underline.range(0, 3).fnt(@18).color(@"random");
UIButton *button1 = Button.str(title).insets(5, 10).fitSize.border(1).xy(20, 150).onClick(^(UIButton *btn) {
//Exp() 可在任何位置執(zhí)行任意代碼
quiz.text = Str(@"%d+%d=%d", 1, 1, Exp(btn.tag += 1));
[quiz sizeToFit];
});
UIButton *button2 = Button.str(@"HAT").highColor(@"brown").img(@"hat").gap(8);
button2.xywh(button1.frame).x(button1.maxX + 10).borderRadius(5).bgImg(@"blue,0.5").highBgImg(@"orange");
//.highBgImg() 可以用來(lái)設(shè)置 UIButton 的 highlightedBackgroundColor,這是一個(gè)非常有用的功能

id pinField = TextField.x(button1.x).y(button1.maxY + 15).wh(170, 30).onChange(^(NSString *text) {
//這里的 self 已經(jīng)自動(dòng)做了 weakify 處理, 不用擔(dān)心會(huì)有引用循環(huán)
[(id)[self.view viewWithTag:101] setText:text];
}).numberKeyboard.maxLength(4).hint(@"pin code").fnt(15).roundStyle;
id textView = TextView.xywh(20, 240, 170, 100).border(1).insets(8).hint(@"placeholder").fnt([pinField font]).tg(101);

正如你所看到的,大部分鏈?zhǔn)綄傩赃€是比較簡(jiǎn)單明了的。有一些屬性非常的靈活,可以接受不同類型的參數(shù)。順便說(shuō)一下,View
只是 [UIView new]
的宏定義,Label
只是 [UILabel new]
的宏定義,其他幾個(gè)UI類也一樣(就是類名去掉 UI )。
你可以用 .opacity()
和 .tg()
來(lái)設(shè)置視圖的 alpha 和 tag 值.
你可以用 .x()
, .y()
, .xy()
, .w()
, .h()
, .wh()
, .xywh()
, .cx()
, .cy()
, .cxy()
, .maxX()
, .maxY()
, .maxXY()
等來(lái)設(shè)置視圖的大小和位置。
你可以用 .touchEnabled
, .touchDisabled
, .invisible
來(lái)設(shè)置視圖是否可點(diǎn)和是否可見(jiàn)。
你可以用 .flexibleLeft
, .flexibleRight
, .flexibleTop
, .flexibleBottom
, .flexibleLR
, .flexibleTB
, .flexibleLRTB
, .flexibleWidth
, .flexibleHeight
, .flexibleWH
等來(lái)設(shè)置autoresizingMask。
你可以用 .centerAlignment
, .rightAlignment
等來(lái)設(shè)置對(duì)齊屬性。
你可以用 .fnt()
來(lái)設(shè)置字體,它能接受的參數(shù)跟 Fnt()
一樣。
你可以用 .str()
來(lái)設(shè)置 text 或者 attributedText, 它能接受的參數(shù)跟 Str()
一樣。
你可以用 .img()
, .highImg()u
, .bgImg()
和 .highBgImg()
來(lái)設(shè)置 image, highlightedImage, backgroundImage 和 highlightedBackgroundImage。 他們能接受的參數(shù)跟 Img()
一樣。
你可以用 .tint()
, .color()
, .bgColor()
, .highColor()
來(lái)設(shè)置 tintColor, textColor, backgroundColor 和 highlightedTextColor, 它們能接受的參數(shù)跟 Color()
一樣。
你可以用 .border()
, .borderRadius()
和 .shadow()
來(lái)設(shè)置邊框和陰影。
你可以用 .fitWidth
, .fitHeight
和 .fitSize
來(lái)改變視圖的大小,使它的大小剛好能包含視圖的內(nèi)容。
你可以用 .onClick()
來(lái)給任何視圖添加一個(gè)單擊事件。
至于 UITextField 和 UITextView, 你可以用 .hint()
來(lái)設(shè)置 placeholder, .maxLength()
來(lái)限制輸入文本的長(zhǎng)度, .onChange()
來(lái)添加一個(gè)文本改變事件。
如果是 UIButton, UITextField 和 UITextView, 你還可以使用 .insets()
來(lái)添加一些padding。
這里列出的只是一部分屬性,你可以到對(duì)應(yīng)的擴(kuò)展頭文件里看完整的屬性列表。
UILabel擴(kuò)展
以前如果想給UILabel添加行間距,必須使用NSAttributedString。現(xiàn)在你只需要使用 .lineGap()
設(shè)置一下就行了。
另一個(gè)很有的擴(kuò)展功能是鏈接,你只需要使用 AttStr()
來(lái)創(chuàng)建一個(gè)NSAttributedString, 并標(biāo)記其中一部分為 .linkForLabel
,那么標(biāo)記的那部分自動(dòng)就會(huì)變成鏈接。然后你只需要用 .onLink()
來(lái)給UILabel 添加一個(gè)鏈接點(diǎn)擊事件就行了。
id str = @"Lorem ipsum 20 dolor sit er elit lamet, consectetaur cillium #adipisicing pecu, sed do #eiusmod tempor incididunt ut labore et 3.14 dolore magna aliqua.";
id attStr = AttStr(str).range(0, 5).match(@"lamet").match(@"[0-9.]+").matchHashTag.linkForLabel;
Label.str(attStr).multiline.lineGap(10).xywh(self.view.bounds).onLink(^(NSString *text) {
Log(text);
}).addTo(self.view);

快速的創(chuàng)建約束
有的時(shí)候手動(dòng)修改 frame 會(huì)顯得很麻煩。NerdyUI 提供一些鏈?zhǔn)綄傩院鸵粋€(gè)跟 Masonry 類似的方式來(lái)創(chuàng)建約束。
你可以用 .fixWidth()
, .fixHeight()
, .fixWH()
來(lái)添加寬高約束。
你可以用 .embedIn()
來(lái)把一個(gè)視圖嵌入到它的父視圖里, 這會(huì)添加上下左右的約束。
你可以用 .horHugging()
, .horResistance()
, .verHugging()
, .verResistance()
, .lowHugging
, .lowResistance
, .highHugging
和 .highResistance
來(lái)設(shè)置 contentHuggingPriority 和 contentCompressionResistancePriority。當(dāng)有多個(gè)視圖在 StackView 里時(shí),可以用這些屬性來(lái)設(shè)置允許哪些視圖可以拉伸,哪些視圖不可以拉伸。
對(duì)于更復(fù)雜的約束, 你可以用 .makeCons()
, .remakeCons()
和 .updateCons()
來(lái)設(shè)置約束, 就像Masonry一樣。
ImageView.img(@"macbook").embedIn(self.view).centerMode;
id hello = Label.str(@"HELLO").fnt(@20).wh(80, 80).centerAlignment;
id mac = Label.str(@"MAC").fnt(@20).wh(80, 80).centerAlignment;
//使用 .makeCons() 之前必須把當(dāng)前視圖加到父視圖里,這里使用 .addTo() 來(lái)執(zhí)行此操作
EffectView.darkBlur.fixWH(80, 80).addTo(self.view).makeCons(^{
//在 .makeCons() 里你可以直接使用 make 變量,不需要顯示的定義它
make.right.equal.superview.centerX.constants(0);
make.bottom.equal.superview.centerY.constants(0);
}).addVibrancyChild(hello).tg(101);
EffectView.extraLightBlur.fixWidth(80).fixHeight(80).addTo(self.view).makeCons(^{
make.left.bottom.equal.view(self.view).center.constants(0, 0);
});
EffectView.lightBlur.addTo(self.view).makeCons(^{
make.size.equal.constants(80, 80).And.center.equal.constants(40, 40);
}).addVibrancyChild(mac);
id subImg = Img(@"macbook").subImg(95, 110, 80, 80).blur(10);
ImageView.img(subImg).addTo(self.view).makeCons(^{
make.centerX.top.equal.view([self.view viewWithTag:101]).centerX.bottom.constants(0);
});

快速布局
手動(dòng)給每個(gè)視圖添加約束稍微想一下就知道會(huì)很麻煩。幸好大部分的 UI 可以用 HorStack()
和 VerStack()
來(lái)實(shí)現(xiàn)。使用這兩個(gè)簡(jiǎn)易版 StackView,加上上面介紹的那幾個(gè)屬性,很多時(shí)候你根本不需要手動(dòng)顯示的創(chuàng)建任何約束。
_indexLabel = Label.fnt(17).color(@"darkGray").fixWidth(44).centerAlignment;
_iconView = ImageView.fixWH(64, 64).borderRadius(10).border(Screen.onePixel, @"#CCCCCC");
//用 .preferWidth() 來(lái)設(shè)置 preferredMaxLayoutWidth,有助于提高性能
_titleLabel = Label.fnt(15).lines(2).preferWidth(Screen.width - 205);
_categoryLabel = Label.fnt(13).color(@"darkGray");
_ratingLabel = Label.fnt(11).color(@"orange");
_countLabel = Label.fnt(11).color(@"darkGray");
_actionButton = Button.fnt(@15).color(@"#0065F7").border(1, @"#0065F7").borderRadius(3);
_actionButton.highColor(@"white").highBgImg(@"#0065F7").insets(5, 10);
_iapLabel = Label.fnt(9).color(@"darkGray").lines(2).str(@"In-App\nPurchases").centerAlignment;
//.gap() 會(huì)在每一個(gè)StackView Item 之間添加間隙
id ratingStack = HorStack(_ratingLabel, _countLabel).gap(5);
id midStack = VerStack(_titleLabel, _categoryLabel, ratingStack).gap(4);
id actionStack = VerStack(_actionButton, _iapLabel).gap(4).centerAlignment;
HorStack(
_indexLabel,
_iconView,
@10, //使用NSNumber可在兩個(gè) Item 之間添加間隙
midStack,
NERSpring, //NERSpring是一個(gè)特殊的變量,它相當(dāng)于一個(gè)彈簧,保證actionStack始終停留在最右邊
actionStack
).embedIn(self.contentView, 10, 0, 10, 15);

這里我們模仿 AppStore 排行榜來(lái)創(chuàng)建一個(gè)類似的 Cell 。可以看出 HorStack (橫向) 和 VerStack (豎向) 的用法非常的簡(jiǎn)單。你只需要找出最小的 Stack ,然后把它嵌到上一層的 Stack 里,重復(fù)這個(gè)過(guò)程直到最外層的 Stack 用 embedIn 來(lái)添加到它的父視圖里。最后你還可以給這些視圖加上一些間隙(gap)。
使用 "Debug View Hierarchy" 可以看到這些視圖是怎么嵌套再一起的。

一旦布局完,剩下的就是設(shè)置要顯示的內(nèi)容,其他的都不需要再動(dòng)了。
輕量級(jí) Style
大部分鏈?zhǔn)綄傩远伎梢栽O(shè)置為 style。
//全局Style
Style(@"h1").color(@"#333333").fnt(17);
Style(@"button").fixHeight(30).insets(0, 10).borderRadius(5);
//局部Style
id actionButtonStyle = Style().styles(@"button h1").bgImg(@"red").highBgImg(@"blue").highColor(@"white");
這里我們創(chuàng)建了兩個(gè)全局 Style 和一個(gè)局部 Style。局部 Style 使用 .styles()
來(lái)繼承那兩個(gè)全局 Style。創(chuàng)建完之后,全局 Style 可以使用 Style 名來(lái)全局引用,局部 Style 只能使用變量名來(lái)引用。所有的 UIView(及其子類) 和 NSAttributedString 都可以引用這些 Style。
id foo = Label.styles(@"h1").str(@"hello world");
id bar = Button.styles(actionButtonStyle).str(@"Send Email");
其他
你可以用 PlainTV
和 GroupTV
來(lái)創(chuàng)建靜態(tài)的 UITableView,比如說(shuō)設(shè)置頁(yè)面。
PlainTV(Row.str(@"Row1"), Row.str(@"Row2"), Row.str(@"Row3")).embedIn(self.view);
你可以用 Alert
和 ActionSheet
來(lái)創(chuàng)建并顯示 UIAlert 和 UIActionSheet。
Alert.title(@"Title").message(@"Message").action(@"OK",^{}), cancel(@"Cancel").show();
ActionSheet.title(@"Title").message(@"Message").action(@"OK",^{}), cancel(@"Cancel").show();
對(duì)于NSArray, 我們提供了 .forEach()
, .map()
, .filter()
和 .reduce()
等這幾個(gè)鏈?zhǔn)綄傩浴?/p>
id result = @[@1, @2, @3, @4].map(^(NSInteger n) {
return n * 2;
}).filter(^(NSInteger n) {
return n < 5;
}).reduce(^(NSInteger ac, NSInteger n) {
return ac + n;
});
注意
在鏈?zhǔn)綄傩岳镏苯邮褂弥形淖址A繒?huì)導(dǎo)致后續(xù)的自動(dòng)補(bǔ)全提示失效,一個(gè)解決方案是把中文字符串單獨(dú)拿出來(lái)定義為一個(gè)變量,或者把 .str()
, .hint()
等 放在最后面。
當(dāng)你使用 .onClick()
, .onLink()
, .onChange()
和 .onFinish()
時(shí), 里面的 self
已經(jīng)做了 weakify 處理了,所以你不需要擔(dān)心會(huì)有引用循環(huán)問(wèn)題。有時(shí)候你可能需要對(duì)它做個(gè)強(qiáng)引用來(lái)保證它不會(huì)被提前釋放。這幾個(gè)屬性除了可以傳一個(gè) block 之外,還可以傳一個(gè)方法名來(lái)作為回調(diào)方法。
NerdyUI 使用了非常多的宏定義和類別方法,而且為了方便使用沒(méi)添加任何前綴。雖然所有的名字都是經(jīng)過(guò)精心挑選的,不排除有跟您自己代碼或其他第三方庫(kù)沖突的可能,請(qǐng)注意。
用CocoaPods安裝
pod "NerdyUI"