注册
iOS

iOS NerdyUI and Cupcake

NerdyUI 使用小技巧

前言

首先本文并不是完整的使用说明,不会对每个属性的用法都面面俱到。如果您想了解更多信息,可以到对应的头文件中查看。这里列出了一些在实际项目中可能会用到的小技巧以及注意事项,希望能对您有所帮助。如果看完觉得有用,麻烦点个赞。如果觉得值得一试,麻烦到 github 给个星,让我有继续写下去的动力。下一篇将解释 NerdyUI 实现上的一些小技巧,敬请期待。

如果您还不知道 NerdyUI 是什么,请先移步这里

Str

  1. .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
  2. .subFrom() 和 .subTo() 用来截取子串,你可以传一个索引或字符串。

     @"hello".subFrom(2);         //"llo"
    @"hello".subFrom(@"l"); //"llo"
    @"hello".subTo(2); //"he"
    @"hello".subTo(@"ll"); //"he"
  3. .subMatch() 和 .subReplace() 可用正则表达式来查找和替换子串。

     @"pi: 3.13".subMatch(@"[0-9.]+");               //"3.13"
    @"pi: 3.13".subReplace(@"[0-9.]+", @"3.14"); //"pi: 3.14"

AttStr

  1. 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);
  2. NSAttributedString 里能包含图片这个事实打开了无限的可能,很多之前要用用多个 Label 和 ImageView 才能实现的 UI 用 AttStr 可以很轻易的搞定。

     AttStr(@"A hat ", Img(@"hat"), @" and a moose", Img(@"moose");
  3. 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() 可连续使用,表示同时选取多个子串。

  4. 使用 .lineGap() 可以设置行间距。但你应该很少会用到,因为 Label 也有一个 .lineGap() 快捷属性。.linkForLabel 只适用于 Label,不适用于其他视图。

Img

  1. 给 Img() 传色值的话会返回一个 1x1 大小的图片,这在大部分情况貌似都没什么用。除了 Button 的 .bgImg() 和 .highBgImg(),因为 Button 的 backgroundImage 会自动拉伸占满整个视图。

     Img(@"red").resize(100, 100);        //100x100 大小的红色图片
  2. .stretchable 会返回一个可拉伸的图片,拉伸位置在图片中心点。如果你想更具体的控制可拉伸区域,可以使用 .tileInsets() 和 .stretchInsets()

     Img(@"button-bg").stretchable;    //等于 Img(@"#button-bg");
    Img(@"pattern").tileInsets(0); //平铺图片
  3. .templates 和 UIView 的 .tint() 配合可以用来给图片上色。

    ImageView.img(Img(@"moose").templates).tint(@"red");

Color

  1. 你可以用 .opacity() 来修改 Color 的 alpha 值:

     Color(@"red").opacity(0.5);        //等于 Color(@"red,0.5");
  2. 你可以用 .brighten().darken().saturate().desaturate() 和 .hueOffset() 等来修改颜色。

     View.wh(100, 100).bgColor(@"#289DCE").onClick(^(UIView *v) {
    v.bgColor(v.backgroundColor.darken(0.2)); //模拟点击变暗效果
    });

Screen

  1. 你可以用 Screen.sizeScreen.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

  1. 如果你想设置一个视图的大小,可以用.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)等等。

  2. 当你想给一个视图设置 border 时,你可只传一个宽度 .border(2), 或者同时带上一个颜色 .border(2, @"red")。如果你已经有一个 UIColor 对象,那么也可以直接传这个对象 .border(2, borderColor),这对于 .tint().color() 和 .bgColor() 等也适用。

  3. 使用 .borderRadius() 会自动把 masksToBounds 设为 YES(如果没有设置阴影的话)。shadow() 默认向下投影,它有几种形式:

     .shadow(0.6);            //shadowOpacity
    .shadow(0.6, 2); //shadowOpacity + shadowRadius
    .shadow(0.3, 3, 3, 3); //shadowOpacity + shadowRadius + shadowOffsetXY
  4. .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()等。

  5. 把一个视图添加到另一个视图里有三种方式:

     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(),后面会说到。

  6. 如果你习惯于手动布局,那么你可能会经常用到 .fitSize、 .fitWidth 和 .fitHeight 来改变视图的大小,用 .flexibleLeft、 .flexibleRight ... .flexibleWH等来设置 autoresizingMask。

    如果你习惯使用 AutoLayout, 则 .fixWidth().fixHeight()、 .fixWH()、 .makeCons()、 remakeCons() 和 updateCons() 等会是你的好朋友。.fixWidth() 等3个内部使用了 .remakeCons() 来设置宽高约束,所以你可以重复使用它们而不用担心会引起约束冲突。

Label

  1. 你可以用 .str() 来设置 text 或者 attributedText。同时你还可以直接传内置类型,省去了转换为字符串的过程:.str(1024)

  2. .fnt() 和 .color() 可以直接传 UIFont 或 UIColor 对象。

  3. .highColor() 可以用来设置 highlighted 状态下的字体颜色,比如 Cell 被选中时。

  4. 允许多行可以用 .lines(0) 或者 .multiline

  5. 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

  1. .img() 还会自动把当前视图的大小设置为图片的大小(如果你没设置过 frame 的话)。

     id iv1 = ImageView.img(@"cat");                //iv1 的大小等于图片的大小
    id iv2 = ImageView.wh(50,50).img(@"cat"); //iv2 的大小等于(50,50)

    .img() 和 .highImg() 还可以接受图片数组。

  2. 你可以用 .aspectFit.aspectFill 和 .centerMode 来设置 contentMode。

Button

  1. Button 标题默认为一行,可以使用 .multiline 来让它支持多行显示。

     Button.str(@"hello\nhow are you").multiline;
  2. 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 的话)。

  3. 因为 UIButton 里带有一个 UILabel 和 一个 UIImageView,很适合用来创建这样的 UI:“一个图标后面跟着一段文字” 或者 “一段文字后面跟着一个图标”,并且图标和文字都可点击。

     //评论图标后跟着评论数
    .img(@"comment_icon").str(commentCount).gap(10);
    //"查看更多"后跟着向右箭头
    .img(@"disclosure_arrow").str(@"查看更多").gap(10).reversed;

    使用 .gap() 可在 image 和 title 之间加上一些间隙。使用 .reversed 可以调换 image 和 title 的位置。

  4. 有的时候你可能想在按钮内容和边框之间留一点空间,那么可以使用 .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() 的可变参数一样。

  5. 组合的使用 .borderRadius()、 .border()、 .color()、 .highColor()、 .bgImg()、 .highBgImg() 、.insets() 以及 AttStr() 等,可以创建出各种各样的按钮。

Constarints

  1. 一个完整的 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);

    可以看到到最后变得非常的精简,但可读性也变得很差了。这就需要各位自己权衡了。

  2. 前面说过如果没有指定 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);
  3. .priority() 可用来设置优先级。.identifier() 可用来设置标识。

  4. 使用 .makeCons().remakeCons() 和 .updateCons() 前必须把当前视图加到父视图里。

     .addTo(superView).makeCons(^{});

TextField / TextView

  1. 你可以用 .hint() 来设置 placeholder, .maxLength() 来限制输入长度。这两个对 UITextField 和 UITextView 来说几乎是标配,奇怪的是系统默认只支持设置 UITextField 的 placeholder。

     .hint(@"Enter your name");      //使用默认的大小和颜色

    id att = AttStr(@"Enter your name").fnt(15).color(@"#999");
    .hint(att); //使用自定义的大小和颜色
  2. .onChange() 会在文本改变时回调,.onFinish() 会在点击键盘上的 return button 时回调。.insets() 的用法跟 UIButton 一样。UITextView 一个不一样的地方在于它默认是有 insets 的,如果你不想要,可以用 .insets(0) 来清空。

  3. 你可以用 .becomeFocus 来获取输入焦点。

HorStack / VerStack

  1. HorStack() 默认的对齐方式是 centerAlignment,VerStack() 默认的对齐方式是 leftAlignment。它们的用法类似于 UIStackView 及 Android 的 LinearLayout。

  2. 如果你设置了 Stack 的宽高约束,那么当 Stack 里子视图的宽度总和或高度总和小于 Stack 本身的宽或高时,有个子视图将会被拉伸。当 Stack 里子视图的宽度总和或高度总和大于 Stack 本身的宽或高时,有个子视图将会被压缩。对于使用 intrinsicContentSize 的子视图来说,你可以通过 .horHugging()、 .verHugging()、 horResistance().verResistance()、 .lowHugging 和 .lowResistance 等来修改 contentHuggingPriority 和 contentCompressionResistancePriority 的值,进而控制哪个子视图可以被拉伸或压缩。对于第一种情况,你还可以使用 NERSpring, 它相当于一个弹簧,会占用尽可能多的空间,这样所有的子视图都不会被拉伸。

  3. 如果你没有设置 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 的方式来布局。

  4. .gap() 会在每个子视图之间添加相同的间隙。@(n) 会在两个子视图之间添加间隙,这就允许不同的子视图之间有不同的间隙。

  5. 可以通过 -addArrangedSubview:、 -insertArrangedSubview:atIndex:、 -removeArrangedSubview: 和 removeArrangedSubviewAtIndex: 来添加或删除子视图。如果想临时隐藏子视图,可以直接设置子视图的 hidden 属性,这是一个非常好用的功能。

Alert / ActionSheet

  1. 可以同时有多个 Action 按钮,其中 .action() 和 . destructiveAction() 必须传标题和回调 block, .cancelAction() 可以只传一个标题:

     Alert.action(@"Action1", ^{

    }).action(@"Action2", ^{

    }).action(@"Action3", ^{

    }).destructiveAction(@"Delete", ^{

    }).cancelAction(@"Cancel").show();
  2. .title().message() 和 .action() 有个隐藏的功能是可以传 NSAttributedString,这就表示它们的显示样式是可以修改的。不过这不是官方提供的功能,可能只在某一些版本的系统上有效,不推荐大家使用。

  3. 使用 .tint() 可以改变所有普通按钮的字体颜色,这是系统提供的功能。

  4. 最后必须调用 .show() 才能显示出来。

Style

  1. 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);
  2. 全局 Style 一般在程序启动的时候设置,比如 -application:didFinishLaunchingWithOptions: 或者 +load 里。

最后

  1. 链式属性分为两种:一种带参数,比如 .color(@"red"),一种不带参数,比如 .centerAlignment。如果最后一个属性是不带参数的属性,且它的返回值没有赋值给一个变量,那么那么编译器将给出警告。你可以使用 .End() 来消除警告。

     UILabel *someLabel = ...;
    ...
    someLabel.str(newString).fitSize; //Warning: Property access result unused

    someLabel.str(newString).fitSize.End(); //no more warning
  2. 尽可能的使用 id,如果后续不需要再访问某个变量的属性,定义为 id 可以减少不少代码。

  3. 多考虑使用 NSAttributedString。因为 AttStr() 的存在,使得创建 NSAttributedString 变得非常简单。并且系统控件早就全面的支持 NSAttributedString 了。

  4. 学会使用 StackView 或 LinearLayout 的方式来思考问题,即同时对几个视图进行布局而不是对每个视图单独进行布局。

  5. 学会使用特殊字符和表情符号,有一些图标乍一看像是图片,但是其实是可以使用特殊字符或表情来表示的。Unicode 提供了非常多的特殊字符,像是 ⚽︎♠︎♣︎☁︎☃☆★⚾︎◼︎▶︎✔︎✖︎♚✎✿✪ 等等,最重要的一点是这些图标就像普通文字一样可以改变大小和颜色。

  6. 如果发现有一些属性没找到,请更新到最新版本。

0 个评论

要回复文章请先登录注册