iOS NerdyUI and Cupcake
NerdyUI 使用小技巧
前言
首先本文并不是完整的使用说明,不会对每个属性的用法都面面俱到。如果您想了解更多信息,可以到对应的头文件中查看。这里列出了一些在实际项目中可能会用到的小技巧以及注意事项,希望能对您有所帮助。如果看完觉得有用,麻烦点个赞。如果觉得值得一试,麻烦到 github 给个星,让我有继续写下去的动力。下一篇将解释 NerdyUI 实现上的一些小技巧,敬请期待。
如果您还不知道 NerdyUI 是什么,请先移步这里。
Str
.a()可用来拼接字符串,.ap()可用来拼接路径。它们能接受的参数跟Str()一样。传 nil 的话则什么事都不做,很适合用来拼接多个字符串。@"1".a(@"2").a(3).a(nil).a(4.0f).a(@5).a(@"%d", 6); //@"123456"
Str(province).a(city).a(district).a(address); //不用担心有的变量可能为 nil.subFrom()和.subTo()用来截取子串,你可以传一个索引或字符串。@"hello".subFrom(2); //"llo"
@"hello".subFrom(@"l"); //"llo"
@"hello".subTo(2); //"he"
@"hello".subTo(@"ll"); //"he".subMatch()和.subReplace()可用正则表达式来查找和替换子串。@"pi: 3.13".subMatch(@"[0-9.]+"); //"3.13"
@"pi: 3.13".subReplace(@"[0-9.]+", @"3.14"); //"pi: 3.14"
AttStr
AttStr()可以把多个 NSString、NSAttributedString 和 UIImage 拼接成一个 NSAttributedString。后面设置的属性默认会覆盖前面设置的相同属性,可以使用.ifNotExists来避免这种情况。.color(@"red").color(@"blue"); //蓝色
.color(@"red").ifNotExists.color(@"blue"); //红色
AttStr(
@"small text, ",
AttStr(@"large text, ").fnt(@40),
AttStr(@"red small text, ").color(@"red"),
Img(@"moose"),
@"small text"
).ifNotExists.fnt(20);NSAttributedString 里能包含图片这个事实打开了无限的可能,很多之前要用用多个 Label 和 ImageView 才能实现的 UI 用 AttStr 可以很轻易的搞定。
AttStr(@"A hat ", Img(@"hat"), @" and a moose", Img(@"moose");AttStr 的属性默认会应用到整个字符串,你可以用
.range()、.match()、.matchNumber、.matchURL、.matchHashTag和.matchNameTag等来缩小范围。id str = @"Hello @Tim_123";
AttStr(str).color(@"blue"); //整个字符串都为蓝色
AttStr(str).range(0, 5).color(@"blue"); //"Hello" 为蓝色
AttStr(str).match(@"Tim").color(@"blue"); //"Tim" 为蓝色
AttStr(str).matchNumber.color(@"blue"); //"123" 为蓝色
AttStr(str).matchNameTag.color(@"blue"); //"@Time_123" 为蓝色
AttStr(str).range(0, 3).range(-3, 3).match(@"@").color(@"blue");
//"Hel", "@", "123" 为蓝色.match()可以使用正则表达式,负数的 range 表示从尾部往前数。.range()和.match()可连续使用,表示同时选取多个子串。使用
.lineGap()可以设置行间距。但你应该很少会用到,因为 Label 也有一个.lineGap()快捷属性。.linkForLabel只适用于 Label,不适用于其他视图。
Img
给
Img()传色值的话会返回一个 1x1 大小的图片,这在大部分情况貌似都没什么用。除了 Button 的.bgImg()和.highBgImg(),因为 Button 的 backgroundImage 会自动拉伸占满整个视图。Img(@"red").resize(100, 100); //100x100 大小的红色图片.stretchable会返回一个可拉伸的图片,拉伸位置在图片中心点。如果你想更具体的控制可拉伸区域,可以使用.tileInsets()和.stretchInsets()。Img(@"button-bg").stretchable; //等于 Img(@"#button-bg");
Img(@"pattern").tileInsets(0); //平铺图片.templates和 UIView 的.tint()配合可以用来给图片上色。
ImageView.img(Img(@"moose").templates).tint(@"red");
Color
你可以用
.opacity()来修改 Color 的 alpha 值:Color(@"red").opacity(0.5); //等于 Color(@"red,0.5");你可以用
.brighten()、.darken()、.saturate()、.desaturate()和.hueOffset()等来修改颜色。View.wh(100, 100).bgColor(@"#289DCE").onClick(^(UIView *v) {
v.bgColor(v.backgroundColor.darken(0.2)); //模拟点击变暗效果
});
Screen
你可以用
Screen.size,Screen.width和Screen.height来访问屏幕大小。Screen 还有一个比较有用的属性是Screen.onePixel, 它始终返回一个像素的大小而不管是在什么设备上。比如设计师可能要求 App 里的分割线都是一个像素的大小,那么你就可以这么用:Style(@"separator").wh(Screen.width, Screen.onePixel).bgColor(@"#d9d9d9");
...
id s1 = View.styles(@"separator");
id s2 = View.styles(@"separator").x(15).w(Screen.width - 30);
View
如果你想设置一个视图的大小,可以用
.wh(50, 50)。但如果你想让一个它的等于另一个视图的大小呢,你可以这么写.wh(otherView.w, otherView.h), 或者更简单一点.wh(otherView.wh), 这是因为.wh()既可以接受两个 CGFloat, 也可以接受一个 CGSize。.xy()、.cxy()、.maxXY()和.xywh()也与此类似,比如.cxy(otherView.center)、.xywh(otherView.frame)和.xywh(otherView.xy, 50, 50)等等。当你想给一个视图设置 border 时,你可只传一个宽度
.border(2), 或者同时带上一个颜色.border(2, @"red")。如果你已经有一个 UIColor 对象,那么也可以直接传这个对象.border(2, borderColor),这对于.tint()、.color()和.bgColor()等也适用。使用
.borderRadius()会自动把 masksToBounds 设为 YES(如果没有设置阴影的话)。shadow()默认向下投影,它有几种形式:.shadow(0.6); //shadowOpacity
.shadow(0.6, 2); //shadowOpacity + shadowRadius
.shadow(0.3, 3, 3, 3); //shadowOpacity + shadowRadius + shadowOffsetXY.onClick()可以用来给任意视图添加一个单击手势,如果这个视图是一个 UIButton,则它使用的是 Button 的 UIControlEventTouchUpInside 事件。使用 onClick 时还会自动把 userInteractionEnabled 设为 YES,毕竟当你给一个 UILabel 或者 UIImageView 添加单击事件时,你想让它们可以点击。你可以传一个 block 来作为回调方法,最简单的形式就是
.onClick(^{ ... })。 onClick 已经自动对self做了 weakify 处理,虽然标准做法是要在 block 里对self再做个强引用,防止它提前释放。但大部分情况下你都不需要这么做,因为很多时候self对应的都是当前视图的父视图或者它所在的 ViewController,而它们是不会提前释放的。如果你还是不放心,那么你可以这么写:.onClick(^{ typeof(self) strongSelf = self; ... });如果需要在 block 里访问当前视图,你不能这么写:
UIView *box = View.onClick(^{
box.bgColor(@"blue"); //box为nil,因为此时onClick还没返回
});正确写法应该是:
UIView *box = View.onClick(^(UIView *box) {
box.bgColor(@"blue"); //使用的是 block 参数
});如果回调代码比较多,或者你更喜欢传统的 target-action 方式,那么你可以这么用:
.onClick(@"boxDidTap") //target 默认为 self,action 为字符串,请小心拼写
.onClick(@"boxDidTap:") //如果你需要当前视图作为参数的话这里提到的
.onClick()的用法同样适用于.onChange()、.onFinish()和.onLink()等。把一个视图添加到另一个视图里有三种方式:
parentView.addChild(view1, view2, view3, ...); //使用 addChild 添加多个子视图
view1.addTo(parentView); //使用 addTo 加到父视图里
view1.embedIn(parentView); //使用 embedIn 加到父视图里,会同时添加上下左右的约束.embedIn()可以有额外的参数,用来设置距离父视图上下左右的偏移量:.embedIn(parentView, 10, 20, 30, 40); //上:10, 左:20, 下:30, 右:40
.embedIn(parentView, 10, 20, 30); //上:10,左右:20,下:30
.embedIn(parentView, 10, 20); //上下:10, 左右:20
.embedIn(parentView, 10); //上下左右:10
.embedIn(parentView); //上下左右:0这中用法跟 HTML 里的 Margin 和 Padding 类似。如果有某些方向你不想加约束的话,你可以用
NERNull代替:.embedIn(parentView, 10, 20, NERNull, NERNull); //上:10,左:20
.embedIn(parentView, 10, NERNull); //上下:10.embedIn()这种可变参数的用法同时也适用于.insets(),后面会说到。如果你习惯于手动布局,那么你可能会经常用到
.fitSize、.fitWidth和.fitHeight来改变视图的大小,用.flexibleLeft、.flexibleRight....flexibleWH等来设置 autoresizingMask。如果你习惯使用 AutoLayout, 则
.fixWidth()、.fixHeight()、.fixWH()、.makeCons()、remakeCons()和updateCons()等会是你的好朋友。.fixWidth()等3个内部使用了.remakeCons()来设置宽高约束,所以你可以重复使用它们而不用担心会引起约束冲突。
Label
你可以用
.str()来设置 text 或者 attributedText。同时你还可以直接传内置类型,省去了转换为字符串的过程:.str(1024)。.fnt()和.color()可以直接传 UIFont 或 UIColor 对象。.highColor()可以用来设置 highlighted 状态下的字体颜色,比如 Cell 被选中时。允许多行可以用
.lines(0)或者.multiline。Label 链接的默认颜色是蓝色,你可以改成其他颜色:
AttStr(@"hello world").match(@"world").linkForLabel.color(@"red"); //红色链接链接选中的样式也可以修改:
//修改单个 Label 的样式
label.nerLinkSelectedBorderRadius = 0;
label.nerLinkSelectedColor = [UIColor blueColor];
//全局修改
[UILabel setDefaultLinkSelectedBackgroundColor:[UIColor blueColor] corderRadius:0];因为 UILabel 默认是不接受事件的,你必须使用
.touchEnabled或者.onLink()才能点击链接。因为.onLink()也会把 userInteractionEnabled 设为 YES。
ImageView
.img()还会自动把当前视图的大小设置为图片的大小(如果你没设置过 frame 的话)。id iv1 = ImageView.img(@"cat"); //iv1 的大小等于图片的大小
id iv2 = ImageView.wh(50,50).img(@"cat"); //iv2 的大小等于(50,50).img()和.highImg()还可以接受图片数组。你可以用
.aspectFit、.aspectFill和.centerMode来设置 contentMode。
Button
Button 标题默认为一行,可以使用
.multiline来让它支持多行显示。Button.str(@"hello\nhow are you").multiline;Button 的
.bgImg()和.highBgImg()非常的灵活和好用。.bgImg(@"btn-normal").highBgImg(@"btn-high"); //使用图片
.bgImg(@"#btn-normal").highBgImg(@"#btn-high"); //使用可拉伸的图片
.bgImg(@"red").highBgImg(@"blue"); //使用颜色之所以用
.bgImg()而不是.bgColor()来设置按钮背景颜色是因为后者在 Cell 选中时会被清空。.bgImg()跟.img()一样会把当前视图的大小设置为图片的大小(如果你没设置过 frame 的话)。因为 UIButton 里带有一个 UILabel 和 一个 UIImageView,很适合用来创建这样的 UI:“一个图标后面跟着一段文字” 或者 “一段文字后面跟着一个图标”,并且图标和文字都可点击。
//评论图标后跟着评论数
.img(@"comment_icon").str(commentCount).gap(10);
//"查看更多"后跟着向右箭头
.img(@"disclosure_arrow").str(@"查看更多").gap(10).reversed;使用
.gap()可在 image 和 title 之间加上一些间隙。使用.reversed可以调换 image 和 title 的位置。有的时候你可能想在按钮内容和边框之间留一点空间,那么可以使用
.insets():.str(@"Done").insets(5, 10).fitSize; //宽高跟着 title 的变化而变化
.str(@"Done").insets(5, 10); //autolayout version
.str(@"Done").h(45).insets(0, 10).fitWidth; //高度固定,宽度变化
.str(@"Done").fixHeight(45).insets(0, 10); //autolayout version.insets()还有一个妙用就是当按钮的背景图片带有阴影时,title 的显示位置会不太对,这时候就可以用.insets()来调整。 它能接受的参数跟.embedIn()的可变参数一样。组合的使用
.borderRadius()、.border()、.color()、.highColor()、.bgImg()、.highBgImg()、.insets()以及AttStr()等,可以创建出各种各样的按钮。
Constarints
一个完整的 NSLayoutConstraint 必须包含这个公式里的全部要素:
view1.attr1 [= , >= , <=] view2.attr2 * multiplier + constant;所以当您使用
.makeCons()来创建约束时,也必须包含这些要素://让当前视图的左边和上边等于父视图的左边和上边
make.left.equal.view(superview).left.multipliers(1).constants(0);
make.top.equal.view(superview).top.multipliers(1).constants(0);
//让当前视图的大小等于 view2 的大小
make.width.equal.view(view2).width.multipliers(1).constants(0);
make.height.equal.view(view2).height.multipliers(1).constants(0);可以看到要写不少代码,幸好这里面很多属性都有默认值,我们可以一步步的精简它们:
//1. 如果有多个约束同时涉及到 view1 和 view2,则可以把它们合并在一起
make.left.top.equal.view(superview).left.top.multipliers(1, 1).constants(0, 0);
make.width.height.equal.view(view2).width.height.multipliers(1, 1).constants(0, 0);
//2. 如果 multipliers 和 constants 的参数都是一样的,则可以把它们合并成一个
make.left.top.equal.view(superview).left.top.multipliers(1).constants(0);
make.width.height.equal.view(view2).width.height.multipliers(1).constants(0);
//3. 如果 attr1 和 attr2 是一样的,则可以省略 attr2
make.left.top.equal.view(superview).multipliers(1).constants(0);
make.width.height.equal.view(view2).multipliers(1).constants(0);
//4. multipliers 的默认值是 1, constants 的默认值是 0,所以它们也可以省略掉
make.left.top.equal.view(superview);
make.width.height.equal.view(view2);
//5. 同时设置 width 和 height 的话可以用 size 来表示
make.left.top.equal.view(superview);
make.size.equal.view(view2);
//6. relation 默认为 equal,所以也可以省略掉(坏处是可读性会降低)
make.left.top.view(superview);
make.size.view(view2);
//7. 如果没指定 view2,则默认为父视图
make.left.top; //虽然很奇怪,但你可以这么写。不过这时候会有警告,因为我们没用到返回值。
make.size.view(view2);
//8. 为了消除警告,可以使用 End() 结尾
make.left.top.End();
make.size.view(view2);
//或者用 And 把它们拼接在一起
make.left.top.And.size.view(view2);可以看到到最后变得非常的精简,但可读性也变得很差了。这就需要各位自己权衡了。
前面说过如果没有指定 view2, 则默认为父视图。这其实有一个例外,就是涉及到 width 和 height 时:
make.size.equal.constants(100, 200);
make.width.constants(100);
make.height.equal.width.End(); //这里的 equal 不能省略,否则就意义不明了这里设置的都是当前视图的大小。如果想让它们相对于其他视图,则需要显示的指定:
make.width.height.equal.view(view2).height.width.multipliers(0.5);.priority()可用来设置优先级。.identifier()可用来设置标识。使用
.makeCons()、.remakeCons()和.updateCons()前必须把当前视图加到父视图里。.addTo(superView).makeCons(^{});
TextField / TextView
你可以用
.hint()来设置 placeholder,.maxLength()来限制输入长度。这两个对 UITextField 和 UITextView 来说几乎是标配,奇怪的是系统默认只支持设置 UITextField 的 placeholder。.hint(@"Enter your name"); //使用默认的大小和颜色
id att = AttStr(@"Enter your name").fnt(15).color(@"#999");
.hint(att); //使用自定义的大小和颜色.onChange()会在文本改变时回调,.onFinish()会在点击键盘上的 return button 时回调。.insets()的用法跟 UIButton 一样。UITextView 一个不一样的地方在于它默认是有 insets 的,如果你不想要,可以用.insets(0)来清空。你可以用
.becomeFocus来获取输入焦点。
HorStack / VerStack
HorStack()默认的对齐方式是 centerAlignment,VerStack()默认的对齐方式是 leftAlignment。它们的用法类似于 UIStackView 及 Android 的 LinearLayout。如果你设置了 Stack 的宽高约束,那么当 Stack 里子视图的宽度总和或高度总和小于 Stack 本身的宽或高时,有个子视图将会被拉伸。当 Stack 里子视图的宽度总和或高度总和大于 Stack 本身的宽或高时,有个子视图将会被压缩。对于使用 intrinsicContentSize 的子视图来说,你可以通过
.horHugging()、.verHugging()、horResistance()、.verResistance()、.lowHugging和.lowResistance等来修改 contentHuggingPriority 和 contentCompressionResistancePriority 的值,进而控制哪个子视图可以被拉伸或压缩。对于第一种情况,你还可以使用NERSpring, 它相当于一个弹簧,会占用尽可能多的空间,这样所有的子视图都不会被拉伸。如果你没有设置 StackView 的宽高约束,那么它的大小会跟随着子视图的变化而变化。一般只有最外层的 StackView 我们会设置它的宽或高(不管是直接或者间接,比如
.embedIn可能会间接的影响它的宽高)。//宽度等于父视图宽度,高度跟随子视图变化
VerStack(view1, view2, view3).centerAlignment.gap(10).embedIn(self.view, 0, 0, NERNull, 0);
//固定宽高,使用 NERSpring 来避免子视图被拉伸
VerStack(view1, @10, view2, NERSpring, view3, @20, view4).wh(self.view.wh).addTo(self.view);虽然后一个例子我们设置的是frame,但因为 UIView 的 translatesAutoresizingMaskIntoConstraints 默认为 YES,所以也相当于设置了宽高约束。加到 Stack 里的子视图的 translatesAutoresizingMaskIntoConstraints 会被设为 NO,所以只有最外层的 Stack 可以用设置 frame 的方式来布局。
.gap()会在每个子视图之间添加相同的间隙。@(n)会在两个子视图之间添加间隙,这就允许不同的子视图之间有不同的间隙。可以通过
-addArrangedSubview:、-insertArrangedSubview:atIndex:、-removeArrangedSubview:和removeArrangedSubviewAtIndex:来添加或删除子视图。如果想临时隐藏子视图,可以直接设置子视图的 hidden 属性,这是一个非常好用的功能。
Alert / ActionSheet
可以同时有多个 Action 按钮,其中
.action()和. destructiveAction()必须传标题和回调 block,.cancelAction()可以只传一个标题:Alert.action(@"Action1", ^{
}).action(@"Action2", ^{
}).action(@"Action3", ^{
}).destructiveAction(@"Delete", ^{
}).cancelAction(@"Cancel").show();.title()、.message()和.action()有个隐藏的功能是可以传 NSAttributedString,这就表示它们的显示样式是可以修改的。不过这不是官方提供的功能,可能只在某一些版本的系统上有效,不推荐大家使用。使用
.tint()可以改变所有普通按钮的字体颜色,这是系统提供的功能。最后必须调用
.show()才能显示出来。
Style
View(及其子类)、AttStr 和 Style 可同时使用一个或多个 Styles。对 Style 来说,就相当于继承: Style(@"headline").fnt(@20).color(@"#333");
Style(@"round-border").borderRadius(8).border(1, @"red");
AttStr(someString).styles(@"headline");
Label.styles(@"headline round-border"); //使用空格作为分隔符,就像 CSS 一样
id roundHeadline = Style().styles(@"headline round-border").bgColor(@"lightGray");
Button.styles(roundHeadline);全局 Style 一般在程序启动的时候设置,比如
-application:didFinishLaunchingWithOptions:或者+load里。
最后
链式属性分为两种:一种带参数,比如
.color(@"red"),一种不带参数,比如.centerAlignment。如果最后一个属性是不带参数的属性,且它的返回值没有赋值给一个变量,那么那么编译器将给出警告。你可以使用.End()来消除警告。UILabel *someLabel = ...;
...
someLabel.str(newString).fitSize; //Warning: Property access result unused
someLabel.str(newString).fitSize.End(); //no more warning尽可能的使用
id,如果后续不需要再访问某个变量的属性,定义为 id 可以减少不少代码。多考虑使用 NSAttributedString。因为
AttStr()的存在,使得创建 NSAttributedString 变得非常简单。并且系统控件早就全面的支持 NSAttributedString 了。学会使用 StackView 或 LinearLayout 的方式来思考问题,即同时对几个视图进行布局而不是对每个视图单独进行布局。
学会使用特殊字符和表情符号,有一些图标乍一看像是图片,但是其实是可以使用特殊字符或表情来表示的。Unicode 提供了非常多的特殊字符,像是 ⚽︎♠︎♣︎☁︎☃☆★⚾︎◼︎▶︎✔︎✖︎♚✎✿✪ 等等,最重要的一点是这些图标就像普通文字一样可以改变大小和颜色。
如果发现有一些属性没找到,请更新到最新版本。