最好用的 iOS 快速布局UI库

NerdyUI

github: https://github.com/nerdycat/NerdyUI

Cupcake (Swift 版本)

github: https://github.com/nerdycat/Cupcake

相关文章:
NerdyUI 使用小技巧


序言

众所周知,UI在一个App中所占的比重是很大的,如果能快速的布局UI,就能大大的提高App的整体开发效率,NerdyUI正是基于这个理由创建的。

NerdyUI使用非常紧凑的链式语法,提供一些常用但系统控件又缺失的功能,更为简便的约束创建方式和更好理解的布局系统,势必能大大减少您的代码量和开发时间。


快速创建 NSString, UIFont, UIColor, UIImage 和常用的 structs

你可以用 Str() 来转换大部分类型到NSString。同理,你可以用 Log() 来打印大部分的变量。

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() 来创建NSAttributedString。

AttStr(@"hello, 101").match(@"[0-9]+").underline;  //给101加下划线
AttStr(@"A smile ", Img(@"smile"), @" !!");     //带图片的NSAttributedString

你可以用 Fnt() 来创建UIFont。

Fnt(15);                     //[UIFont systemFontOfSize:15]
Fnt(@15);                   //[UIFont boldSystemFontOfSize:15]
Fnt(@"body");               //UIFontTextStyleBody
Fnt(@"Helvetica,15");       //helvetica font with size 15

你可以用 Color() 来创建UIColor。

Color(@"red");              //[UIColor redColor]
Color(@"0,0,255");          //RGB color
Color(@"#0000FF");          //Hex color
Color(@"random");            //random color

//以上这些还可以有一个可选的alpha参数
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() 来创建UIImage。

Img(@"imageName");          //[UIImage imageNamed:@"imageName"]
Img(@"#imageName");        //加上#号会返回一个可拉伸的图片,拉伸位置在图片中心
Img(@"red");                  //返回一个 1x1 大小的红色图片,可用在某些需要纯色图片的地方

你可以用 XY(), WH(), XYWH(), Range(), Insets() 来创建CGPoint, CGSize, CGRect, NSRange, UIEdgeInsets。XYWH()Insets() 支持多种创建方式。

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}

使用这些宏可以简化一些常见类型的创建过程,更重要的是你可以用同样的方式来设置视图的属性值,稍后你就会明白这是什么意思。

快速访问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);

我猜大部分人都有类似的扩展吧

快速的创建UI控件

NerdyUI 使用链式语法来快速的创建和设置 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");
});
view
view
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);
moose
moose
//如果后续不需要再访问 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() 可在任何位置执行任意代码
    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() 可以用来设置 UIButton 的 highlightedBackgroundColor,这是一个非常有用的功能
button
button
id pinField = TextField.x(button1.x).y(button1.maxY + 15).wh(170, 30).onChange(^(NSString *text) {
    //这里的 self 已经自动做了 weakify 处理, 不用担心会有引用循环
    [(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);
input
input

正如你所看到的,大部分链式属性还是比较简单明了的。有一些属性非常的灵活,可以接受不同类型的参数。顺便说一下,View 只是 [UIView new] 的宏定义,Label 只是 [UILabel new] 的宏定义,其他几个UI类也一样(就是类名去掉 UI )。

你可以用 .opacity().tg() 来设置视图的 alpha 和 tag 值.

你可以用 .x(), .y(), .xy(), .w(), .h(), .wh(), .xywh(), .cx(), .cy(), .cxy(), .maxX(), .maxY(), .maxXY() 等来设置视图的大小和位置。

你可以用 .touchEnabled, .touchDisabled, .invisible 来设置视图是否可点和是否可见。

你可以用 .flexibleLeft, .flexibleRight, .flexibleTop, .flexibleBottom, .flexibleLR, .flexibleTB, .flexibleLRTB, .flexibleWidth, .flexibleHeight, .flexibleWH 等来设置autoresizingMask。

你可以用 .centerAlignment, .rightAlignment 等来设置对齐属性。

你可以用 .fnt() 来设置字体,它能接受的参数跟 Fnt() 一样。

你可以用 .str() 来设置 text 或者 attributedText, 它能接受的参数跟 Str() 一样。

你可以用 .img(), .highImg()u, .bgImg().highBgImg() 来设置 image, highlightedImage, backgroundImage 和 highlightedBackgroundImage。 他们能接受的参数跟 Img() 一样。

你可以用 .tint(), .color(), .bgColor(), .highColor() 来设置 tintColor, textColor, backgroundColor 和 highlightedTextColor, 它们能接受的参数跟 Color() 一样。

你可以用 .border(), .borderRadius().shadow() 来设置边框和阴影。

你可以用 .fitWidth, .fitHeight.fitSize 来改变视图的大小,使它的大小刚好能包含视图的内容。

你可以用 .onClick() 来给任何视图添加一个单击事件。

至于 UITextField 和 UITextView, 你可以用 .hint() 来设置 placeholder, .maxLength() 来限制输入文本的长度, .onChange() 来添加一个文本改变事件。

如果是 UIButton, UITextField 和 UITextView, 你还可以使用 .insets() 来添加一些padding。

这里列出的只是一部分属性,你可以到对应的扩展头文件里看完整的属性列表。

UILabel扩展

以前如果想给UILabel添加行间距,必须使用NSAttributedString。现在你只需要使用 .lineGap() 设置一下就行了。

另一个很有的扩展功能是链接,你只需要使用 AttStr() 来创建一个NSAttributedString, 并标记其中一部分为 .linkForLabel,那么标记的那部分自动就会变成链接。然后你只需要用 .onLink() 来给UILabel 添加一个链接点击事件就行了。

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);
label
label

快速的创建约束

有的时候手动修改 frame 会显得很麻烦。NerdyUI 提供一些链式属性和一个跟 Masonry 类似的方式来创建约束。

你可以用 .fixWidth(), .fixHeight(), .fixWH() 来添加宽高约束。

你可以用 .embedIn() 来把一个视图嵌入到它的父视图里, 这会添加上下左右的约束。

你可以用 .horHugging(), .horResistance(), .verHugging(), .verResistance(), .lowHugging, .lowResistance, .highHugging.highResistance 来设置 contentHuggingPriority 和 contentCompressionResistancePriority。当有多个视图在 StackView 里时,可以用这些属性来设置允许哪些视图可以拉伸,哪些视图不可以拉伸。

对于更复杂的约束, 你可以用 .makeCons(), .remakeCons().updateCons() 来设置约束, 就像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() 之前必须把当前视图加到父视图里,这里使用 .addTo() 来执行此操作
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);
});
constraints
constraints

快速布局

手动给每个视图添加约束稍微想一下就知道会很麻烦。幸好大部分的 UI 可以用 HorStack()VerStack() 来实现。使用这两个简易版 StackView,加上上面介绍的那几个属性,很多时候你根本不需要手动显示的创建任何约束。

_indexLabel = Label.fnt(17).color(@"darkGray").fixWidth(44).centerAlignment;
_iconView = ImageView.fixWH(64, 64).borderRadius(10).border(Screen.onePixel, @"#CCCCCC");

//用 .preferWidth() 来设置 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() 会在每一个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可在两个 Item 之间添加间隙
         midStack,
         NERSpring,     //NERSpring是一个特殊的变量,它相当于一个弹簧,保证actionStack始终停留在最右边
         actionStack
).embedIn(self.contentView, 10, 0, 10, 15);
appcell
appcell

这里我们模仿 AppStore 排行榜来创建一个类似的 Cell 。可以看出 HorStack (横向) 和 VerStack (竖向) 的用法非常的简单。你只需要找出最小的 Stack ,然后把它嵌到上一层的 Stack 里,重复这个过程直到最外层的 Stack 用 embedIn 来添加到它的父视图里。最后你还可以给这些视图加上一些间隙(gap)。

使用 "Debug View Hierarchy" 可以看到这些视图是怎么嵌套再一起的。

appcell2
appcell2

一旦布局完,剩下的就是设置要显示的内容,其他的都不需要再动了。

轻量级 Style

大部分链式属性都可以设置为 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");

这里我们创建了两个全局 Style 和一个局部 Style。局部 Style 使用 .styles() 来继承那两个全局 Style。创建完之后,全局 Style 可以使用 Style 名来全局引用,局部 Style 只能使用变量名来引用。所有的 UIView(及其子类) 和 NSAttributedString 都可以引用这些 Style。

id foo = Label.styles(@"h1").str(@"hello world");
id bar = Button.styles(actionButtonStyle).str(@"Send Email");

其他

你可以用 PlainTVGroupTV 来创建静态的 UITableView,比如说设置页面。

PlainTV(Row.str(@"Row1"), Row.str(@"Row2"), Row.str(@"Row3")).embedIn(self.view);

你可以用 AlertActionSheet 来创建并显示 UIAlert 和 UIActionSheet。

Alert.title(@"Title").message(@"Message").action(@"OK",^{}), cancel(@"Cancel").show();
ActionSheet.title(@"Title").message(@"Message").action(@"OK",^{}), cancel(@"Cancel").show();

对于NSArray, 我们提供了 .forEach(), .map(), .filter().reduce() 等这几个链式属性。

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

注意

在链式属性里直接使用中文字符串常量会导致后续的自动补全提示失效,一个解决方案是把中文字符串单独拿出来定义为一个变量,或者把 .str(), .hint() 等 放在最后面。

当你使用 .onClick(), .onLink(), .onChange().onFinish() 时, 里面的 self 已经做了 weakify 处理了,所以你不需要担心会有引用循环问题。有时候你可能需要对它做个强引用来保证它不会被提前释放。这几个属性除了可以传一个 block 之外,还可以传一个方法名来作为回调方法。

NerdyUI 使用了非常多的宏定义和类别方法,而且为了方便使用没添加任何前缀。虽然所有的名字都是经过精心挑选的,不排除有跟您自己代码或其他第三方库冲突的可能,请注意。

用CocoaPods安装

pod "NerdyUI"
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 原文地址:http://www.jianshu.com/p/ac47c4826a19 NerdyUI 最好用的快速...
    默默_David阅读 1,241评论 0 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 翻译自“Collection View Programming Guide for iOS” 0 关于iOS集合视...
    lakerszhy阅读 3,723评论 1 22
  • 2016-12-14 Yesterday's mistake 昨天是看着英文的原文改写的, 所有点没动脑. 今天读...
    愿景力阅读 413评论 0 1
  • 记得暑假在泰国游玩的后几天,我们住在离芭东海滩很近的一个别墅区里,这里的每栋别墅都很大,依山而建,造型各异,...
    双鱼的Antony阅读 1,057评论 0 50