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 提供了非常多的特殊字符,像是 ⚽︎♠︎♣︎☁︎☃☆★⚾︎◼︎▶︎✔︎✖︎♚✎✿✪ 等等,最重要的一点是这些图标就像普通文字一样可以改变大小和颜色。
如果发现有一些属性没找到,请更新到最新版本。