前言
查看本文前可先看:UITableView、UICollectionView列表控件的快速搭建,会对下面某些章节更好的理解。
前因:
1、在 iOS 开发 搭建 UI 的过程中,更多的 是使用 纯手工代码去编写UI,这个有经验的应该都知道; 对于 xib、 storyboard 等可视化搭建 UI 方式其实是比较少用的,新手可能会用的多。主要原因是 这些 可视化 的UI 搭建方式 是不可扩展的,说白了就是一个静态 View,不利于继承的使用,也不利于做 一些动态布局的交互事件。所以日常开发中,都是使用 纯手工代码搭建UI 比较多。
2、那么在 纯手工 写 UI 的过程中,我们常常是 新建多个子控件属性,然后 为逐个为这些子控件通过它的属性,一个一个的设置对于的属性样式,诸如富文本、圆角边框等。
以 部分原生控件 为例,如果单纯用原生的 Api ,你是否需要这样设值:
UILabel* label = [UILabelnew];
label.text=@"asdd";
label.textColor = [UIColor blueColor];
label.font = [UIFont systemFontOfSize:12];
UIButton* button = [UIButtonnew];
[buttonsetTitle:@"adadsas" forState:UIControlStateNormal];
[buttonsetTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
button.titleLabel.font = [UIFont systemFontOfSize:12];
3、又或者 你搭建两个布局,子控件都不同的cell,但是他们当中有部分子控件的样式是一样的,你是否有需要 分别在 两个 cell 中,又重复的 添加 子控件 属性,来一个 UILabel,来一个 UIButton,来一个 UIImageview,然后再去设值,再去布局,不断地周而复始重复这个过程。
当你写的 UI 多了,就会发现写来写去都是写这些东西,花费大量的代码量只为设置一个简单的控件样式,完全没必要,简直是浪费生命。那么怎么样能省却这些麻烦的步骤,将我们的时间花在逻辑处理上,而不是做这种无意义的流水工作呢?
现在,请跟随我的思路,让我向你介绍,我是如何逐步逐步的解决这些操蛋的问题的。
篇幅较长,内容较多,请耐心听我娓娓道来,希望能对你的 iOS 开发有所帮助。
目录:
1、MTWordStyle 的使用;
2、MTBorderStyle 的使用;
3、MTShadowStyle 的使用;
4、MTJianBianStyle 的使用;
5、内层原理:MTViewContentModel 的使用;
6、外层躯壳:MTBaseCell 的使用;
7、样式快速搭建框架 + 列表快速搭建框架 的联合使用;
8、统一样式管理者:MTViewContentManager 的使用;
9、使用总结;
10、最后说两句;
MTWordStyle 的使用:
通过 MTWordStyle 对象,能将所有与 文本相关的 UI属性,以模型的形式集合起来,并通过链式编程的方式进行样式追加,实现对 拥有文本属性的控件(UILabel、UIButton、UITextField、UITextView)的一次性设置。
基本用法:
1、使用 mt_WordStyleMake(wordSize, wordName, wordColor) 构建一个 MTWordStyle 对象,一次性设置文本相关的 3个基本属性。即:字号、文本内容、颜色。示例如下:
mt_WordStyleMake(12, @"哈哈哈哈哈", [UIColor BlackColor]);
// 创建一个 字号为 12、内容为 “哈哈哈哈哈”,字体颜色为 黑色 的文本模型
2、使用 mt_AttributedWordStyleMake(wordSize, wordName, wordColor) 构建一个 MTWordStyle 对象,3个参数同上,不同的是,使用该模型赋值,会为控件赋值富文本的内容,而不是普通文本。
仅仅以上 3个属性 是不能满足日常开发的刁钻需求的,但基本满足 普通文本的显示需要,选这3个基本属性作为便捷方法的创建参数,是因为这 3个属性太常用,涉及文本的肯定绕不开这3个属性。对于一些不太常用的设置,本人采用属性追加的方法:
属性追加用法:
先查看可以追加的属性,如下:
@property (nonatomic,copy,readonly) FontName fontName; // 设置文本字体
@property (nonatomic,copy,readonly) MaximumLineHeight maximumLineHeight; //设置文本的最大行高,常用于多行文本,目的是让控件能尽量文字包裹,不会生成多余的间距
@property (nonatomic,copy,readonly) WordStyles styleList; // 包含其他的样式模型,用于富文本设置
@property (nonatomic,copy,readonly) Range range; //一个确定位置的,需要设置 富文本的 range
@property (nonatomic,copy,readonly) RangeMethod rangeMethod; // 一个不确定位置的,需要通过传入的文本内容,求出需要设置 富文本 range 的blcok回调
@property (nonatomic,copy,readonly) NumberOfLines numberOfLines; //设置文本行数
@property (nonatomic,copy,readonly) LineBreakMode lineBreakMode; //设置断尾方式
@property (nonatomic,copy,readonly) Spacing spacing; //设置字间距,富文本才生效
@property (nonatomic,copy,readonly) LineSpacing lineSpacing; //设置行间距,富文本才生效
@property (nonatomic,copy,readonly) HorizontalAlignment horizontalAlignment; //设置水平对齐方式
@property (nonatomic,copy,readonly) Bold bold; //设置是否加粗
@property (nonatomic,copy,readonly) Thin thin; //设置是否加幼
@property (nonatomic,copy,readonly) UnderLine underLine; //设置下划线样式
1、先后顺序不影响属性值的设置,比如:
mt_WordStyleMake(12, @"asdasd", [UIColor redColor])
.lineBreakMode(NSLineBreakByTruncatingTail)
.horizontalAlignment(NSTextAlignmentCenter);
与
mt_WordStyleMake(12, @"asdasd", [UIColor redColor])
.horizontalAlignment(NSTextAlignmentCenter)
.lineBreakMode(NSLineBreakByTruncatingTail);
是等价的。
2、对于相同的属性追加,新追加的会覆盖后追加的。比如:
mt_WordStyleMake(12, @"asdasd", [UIColor redColor])
.horizontalAlignment(NSTextAlignmentCenter)
.horizontalAlignment(NSTextAlignmentLeft);
最后起作用的是 NSTextAlignmentLeft。
3、富文本的便捷设置。
富文本设置,使用 .styleList([MTWordStyle]) 属性,在这里介绍一个宏的使用:
#define wordStyles(...) styleList(@[__VA_ARGS__])
通过使用 .语法,将 WordStyle 模型的宏标识属性 wordStyles 点出来,再加上(),即可触发该宏的调用,这种特殊技巧将在之后的讲解中越来越常见。使用这个宏的目的,主要是将外层传入数组的形式隐藏起来,使得书写阅读上更清晰明了。示例如下:
mt_AttributedWordStyleMake(12, @"asdasd", [UIColor redColor]).wordStyles(
mt_WordStyleMake(13,nil, [UIColor greenColor]),
mt_WordStyleMake(14,nil, [UIColor blueColor]),
);
这段代码的含义为:
创建一个 富文本模型,其文本内容为 @“asdasd”, 其通用字号为 12,其通用字体颜色为 红色。同时,这段文本包含了 两种文本样式的富文本处理,第一个文本样式 为 字号 13,颜色为绿色;第二个文本样式为字号 14,颜色为蓝色。
即,当你需要在一段文本上,引用特殊的样式处理,就使用 .wordStyles (...),将你需要设置的所有其他样式添加上去,即组合成一个 富文本模型的设置。
说几个注意的点:
1、要使用富文本设置,请使用 mt_AttributedWordStyleMake,而不是 mt_WordStyleMake。
mt_WordStyleMake 只适用与普通文本显示,它设置的文本字号颜色等都是唯一的,即使追加 .wordStyles(...)也是不起作用的。
2、在上面的示例中,注意到 子样式的文本内容,我传入的是 nil;原因是 无论是 富文本还是普通文本,文本内容都是唯一的,只是在这段文本上的样式是一种还是多种的问题。对于富文本,统一将文本内容写在 父样式上,即使子样式设置了文本内容,也是不起作用的,只会将子样式除文本内容外的所有样式属性追加到父样式的文本内容上。如示例中的子样式,最后将作用于父样式的 “asdasd” 文本内容上。
3、子样式 使用普通的 mt_WordStyleMake 设置即可,不需使用 mt_AttributedWordStyleMake,父样式之会拿子样式进行文本设置,而不会再获取子样式的子样式,实际上也没必要这样。
4、以上示例,实质上子样式并没有起作用,原因是并没有指定这两个子样式作用于 父样式文本内容什么位置。需要一个 NSRange 来去确定具体作用的位置,而 range 的追加方式,上面已经提及,上面的示例,补全如下:
mt_AttributedWordStyleMake(12, @"asdasd", [UIColor redColor]).wordStyles(
mt_WordStyleMake(13,nil,[UIColor greenColor])
.range(NSMakeRange(0,1)),
mt_WordStyleMake(14,nil, [UIColor blueColor])
.rangeMethod(^(NSString* text){
return NSMakeRange(3, text.length-1-3);
}),
);
这里 使用了 .range(),针对文本的固定位置追加样式,而对于不确定文本长度的情况,使用 .rangeMethod(^(NSString* text),返回计算得出的 NSRange,这里回调传进来的 text,其实就是父样式的文本内容,即示例中的 @“asdasd” 文本。
这个示例最后设置的富文本样式,得出结果为:
控件上将显示 文本内容为 @"asdasd",普通字号为 12,普通字体颜色为红色的富文本。其中,文本的 第一个 “a” 显示字号为 13,颜色为绿色;文本中的 第二个 “as”显示字号为14,颜色为蓝色。
5、值得一提的是,这个字体样式模型默认是使用系统字体的,如果想使用自定义的字体,可追加 .fontName(NSString) 属性,如:
mt_WordStyleMake(12, @"12321.00", [UIColor redColor]).fontName(@"Barlow-Regular"); //设置文本字体为 Barlow-Regular
当然,前提是你的项目有这个字体才会生效,否则会用回系统默认字体。
通过 MTWordStyle 为控件设置文本样式:
有了前面的知识基础,现在来讲解如何用这个 模型为控件设置文本样式,调用非常简单。
我们知道,iOS 开发中,设计 文本的控件 10个手指也能数完,它们就是:
UILabel、UIButton、UITextField、UITextView 这 4 个控件。
1、现在,暂时统称它们为 textContentView,以便我接下来讲解用法。
调用方式如下:
[textContentView setWordWithStyle:
mt_WordStyleMake(12,@"是不是很爽", [UIColor yellowColor])
];
通过调用 setWordWithStyle:(MTWordStyle) 方法,能快速集成文本样式的属性。这里示例只是展示了一个普通的文本样式模型,你仍然能像上面讲解的那样,根据需要为这个文本模型追加属性,诸如 对齐方式,行高,行数,断行方式等等;也能像上面讲解那样,通过这种方式实现富文本,而不用再写原生那些臃肿的方法。更有甚者,你可以在控件刚创建出来后,就立即为它设置样式,如:
UILabel* label = [[UILabelnew] setWordWithStyle:
mt_WordStyleMake(12,@"是不是很爽", [UIColor yellowColor])
.lineBreakMode(NSLineBreakByTruncatingTail)
.horizontalAlignment(NSTextAlignmentCenter)
.numberOfLines(3)
];
这种设置样式后直接赋值给变量的方式绝对是一步到位,因为 setWordWithStyle:(MTWordStyle) 方法本来返回值就是控件本身。
2、对于按钮控件,设置比较特殊,因为我们都知道,按钮是有状态的。上面的方法 默认是为按钮 的 UIControlStateNormal 状态设置样式,它底层其实是调用了这个方法:
-(instancetype)setWordWithStyle:(MTWordStyle*)styleState:(UIControlState)state;
对于按钮来说,调用 setWordWithStyle: 实质上是调用 setWordWithStyle: styleState:UIControlStateNormal。
通过这个方法,可以为按钮的不同状态设置文本样式,示例如下:
[self setWordWithStyle:mt_WordStyleMake(12, @"哈哈哈哈哈", [UIColor blueColor]) State:UIControlStateHighlighted];
[self setWordWithStyle:mt_WordStyleMake(12,@"哈哈哈哈哈", [UIColorblueColor]) State:UIControlStateDisabled];
[self setWordWithStyle:mt_WordStyleMake(12,@"哈哈哈哈哈", [UIColorblueColor]) State:UIControlStateSelected];
//以下作用相同
[self setWordWithStyle:mt_WordStyleMake(12,@"哈哈哈哈哈", [UIColorblueColor]) State:UIControlStateNormal];
[self setWordWithStyle:mt_WordStyleMake(12, @"哈哈哈哈哈", [UIColor blueColor])];
实际上,如果只是按钮常态下的设置,直接调用 setWordWithStyle: 即可。
3、对于 UITextField,它有一个默认的文本属性,即 placeholder。现在你可以传入一个 文本样式,去改变 textField 的默认文本。如:
[textField setPlaceholderWithStyle:mt_WordStyleMake(13, @"asdasd", [UIColor greenColor])];
通过 setPlaceholderWithStyle:(MTWordStyle) 方法,现在可以直接通过这个模型对象设置默认文本了,回想下写原生的时候作富文本操作是不是经常 NSMutableAttributedString。。。。,需要多少行代码实现简单的富文本,现在直接一个文本样式模型搞定一切。当然,貌似文本框的默认文本只有颜色可以改变,所以通过这个方法,只是简单改变默认文本的颜色。
至此,关于 MTWordStyle 的讲解已完毕,如果你以为这就结束了,很抱歉,这才刚刚开始,这套机制只是实现了简单的文本样式快速创建,文本设置 对于 整个控件样式设置不是全部,但是app开发中非常重要的内容,看到这里 已经可以减少写原生设置文本的大量代码,但还不是最简便的,还有比现在的写法更高效的写法,如果感兴趣,请跟随我的讲解,一步步深入了解这套机制的内容。
MTBorderStyle 的使用:
通过 MTBorderStyle 对象,将UI控件的 边界、圆角的设置变得简便化,示例如下:
mt_BorderStyleMake(10, 2, [UIColor redColor]);
通过 mt_BorderStyleMake(borderWidth, borderRadius, borderColor),一步设置边框宽度,边框圆角以及边框颜色。
追加属性如下:
@property (nonatomic,copy,readonly) Corners corners; //设置 哪几个角需要圆角,默认是 4个角都是圆角
@property (nonatomic,copy,readonly) Weak weak; //设置边界是否为虚线
@property (nonatomic,copy,readonly) Fill fill; //设置是否填充背景,相当于是背景色设置
@property (nonatomic,copy,readonly) MasksToBounds masksToBounds; //设置是否需要切除超出父控件的部分
@property (nonatomic,copy,readonly) ViewSize viewSize; //设置部分圆角时选择性需要,为控件的尺寸
基本上,如果不涉及部分圆角,mt_BorderStyleMake(borderWidth, borderRadius, borderColor) 已经满足基本的边界样式设置使用。
以下举例说明如何设置部分圆角,用回上面的例子,追加属性:
mt_BorderStyleMake(10, 2, [UIColor redColor])
.corners(UIRectCornerTopLeft | UIRectCornerTopRight);
通过追加 .corners(),为控件仅设置 左上和右上的圆角。实际上,部分圆角 是通过 贝塞尔曲线去实现的,它需要一个 CGRect,所以如果没有追加 .viewSize() 的话,默认是去取控件当前的 bounds,如果没有追加,建议是将 边界设置 放在 layoutSubviews 中,因为执行这个方法时,控件的宽高已经确定了。当然,保险起见,追加 viewSize() 更能灵活使用,这样在哪里设置都无所谓。
用回上面的例子,完整的部分圆角设置为:
mt_BorderStyleMake(10, 2, [UIColor redColor])
.corners(UIRectCornerTopLeft | UIRectCornerTopRight)
.viewSize(100, 100);
这样,就完成了 实现控件部分圆角的 边界样式模型 创建了。
通过 MTBorderStyle 为控件设置边界样式:
所有的 UIView 控件,都包含了边界样式的设置,因为这是作为一个 View 的基本样式设置,view 可以没有文本,也可以没有图片,但它一定会有尺寸,所以尺寸存在那么必然有边界存在的可能。
对于所有的 UIView,为其设置边界样式,如下:
[view becomeCircleWithBorder:mt_BorderStyleMake(1, 3, [UIColor redColor])];
通过 becomeCircleWithBorder:(MTBorderStyle),实现 view 的边界设置一步到位。
类似 MTWordStyle,在 为控件 设置 MTBorderStyle 时,也是可以像上面的例子一样追加属性的,还是得敲代码才能弄懂是怎么一回事,在此就不再一一举例了。
MTShadowStyle 的使用:
用于为控件设置阴影样式,用法与上面类似,对比前面两个相对简单,类声明如下:
@interface MTShadowStyle : NSObject
@property(nonatomic,strong) UIColor* shadowColor;
@property(nonatomic,assign) CGFloat shadowOpacity;
@property(nonatomic,assign) CGSize shadowOffset;
@property(nonatomic,assign) CGFloat shadowRadius;
@end
包含阴影设置的 4个属性,即:阴影颜色、阴影不透明度、阴影偏移、阴影圆角。
通过 mt_ShadowStyleMake(CGFloat shadowOpacity,CGFloat shadowRadius,UIColor* shadowColor,CGSize shadowOffset),一步设置这4个属性。
通过 MTShadowStyle 为控件设置阴影样式:
对于所有的 UIView,为其设置边界样式,如下:
[view becomeShadow:mt_ShadowStyleMake(0.5, 4, [UIColor redColor], CGSizeMake(10,10))];
通过 becomeShadow:(MTShadowStyle),实现 view 的阴影设置一步到位。这个方法实质是通过阴影样式模型属性去设置 View 的 layer 对应的属性,原理如下:
-(instancetype)becomeShadow:(MTShadowStyle*)shadowStyle
{
self.layer.shadowColor= shadowStyle.shadowColor.CGColor;
self.layer.shadowOpacity= shadowStyle.shadowOpacity;
self.layer.shadowOffset= shadowStyle.shadowOffset;
self.layer.shadowRadius= shadowStyle.shadowRadius;
return self;
}
返回 self 实质上是为了省却要单独调用方法设置样式的步骤。
MTJianBianStyle 的使用:
用于为任一UIView控件设置渐变背景色,实质原理是在 View 的 layer 上添加一个颜色渐变层。
该类如下:
@interface MTJianBianStyle : NSObject
@property(nonatomic,assign) CGSize viewSize;
@property(nonatomic,strong) UIColor* startColor;
@property(nonatomic,strong) UIColor* endColor;
@property(nonatomic,assign) CGPoint startPoint;
@property(nonatomic,assign) CGPoint endPoint;
-(void)setBackgroundColor:(UIView*)view;
@end
通过 setBackgroundColor:(UIView) 设置控件的渐变色,如:
[mt_jianBianStyleMake([UIColor redColor], [UIColor blueColor], CGPointMake(0,50), CGPointMake(100,50), CGSizeMake(100,100)) setBackgroundColor:view];
通过 mt_jianBianStyleMake(UIColor* startColor,UIColor* endColor,CGPoint startPoint,CGPoint endPoint,CGSize viewSize) 快速创建渐变样式,分别对于设置样式对象的 startColor(开始颜色)、endColor(结束颜色)、startPoint(开始点)、endPoint(结束点)、viewSize(view 的尺寸)。
除了通过这种方式设置背景渐变色外,还有一种方式就是生成渐变色图片,然后将图片添加到View最底层。生成渐变色图片的方法如下:
/**默认线性角度为中线*/
-(void)createJianBianBackgroundColorWithStartColor:(UIColor*)startColor endColor:(UIColor*)endColor;
/**自行调整线性角度*/
-(void)createJianBianBackgroundColorWithStartColor:(UIColor*)startColor endColor:(UIColor*)endColor startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
/**生成渐变图片,默认线性角度为中线*/
-(UIImage*)createJianBianImageWithStartColor:(UIColor*)startColor endColor:(UIColor*)endColor;
-(UIImage*)createJianBianImageWithStartColor:(UIColor*)startColor endColor:(UIColor*)endColor Size:(CGSize)size;
-(UIImage*)createJianBianImageWithStartColor:(UIColor*)startColor endColor:(UIColor*)endColor BorderStyle:(MTBorderStyle*)borderStyle;
/**生成渐变图片,自行调整线性角度*/
-(UIImage*)createJianBianImageWithStartColor:(UIColor*)startColor endColor:(UIColor*)endColor startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;
-(UIImage*)createJianBianImageWithStartColor:(UIColor*)startColor endColor:(UIColor*)endColor startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint Size:(CGSize)size;
-(UIImage*)createJianBianImageWithStartColor:(UIColor*)startColor endColor:(UIColor*)endColor startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint BorderStyle:(MTBorderStyle*)borderStyle;
这些方法的区别在于:
1、最顶两个方法是直接将生成的渐变色图片通过 UIImageView 添加到 view 的底层,区别是第一个缺省起始点与结束点的设置,默认为 view 的从左到右水平中线,实际上第一个方法最后会走第二个方法。注意设置前请确保 view 有尺寸,否则无法生成对应view尺寸的图片。
2、除了头两个方法外,底下都是生成 渐变色图片的方法,区别在于也是作了起始点与结束点的缺省,默认同第1点;
3、参数 Size 是用于给定固定的图片尺寸,默认缺省的话取当前 View 的尺寸;
4、borderStyle 是用于设置图片的圆角,尺寸等,缺省的话,如果缺省 size,则生成一个 viewSize等于当前 view 的 borderStyle,否则,生成 viewSize 为 size 的borderStyle。
5、以上所有方法实际上底层都是调用最后一个方法:
-(UIImage*)createJianBianImageWithStartColor:(UIColor*)startColor endColor:(UIColor*)endColor startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint BorderStyle:(MTBorderStyle*)borderStyle;
实质上 线性渐变图片的生成也是通过 borderStyle 去生成的,图片的尺寸通过 borderStyle 的 viewSize 去给定的。
内层原理:MTViewContentModel 的使用
MTBaseViewContentModel 使用:
通过 MTBaseViewContentModel 创建一个代表样式的模型对象。
通过 mt_content() 创建一个MTBaseViewContentModel 对象。使用如下:
mt_content(
@"text",
mt_BorderStyleMake(1,2, [UIColorblackColor]),
mt_WordStyleMake(12,@"asdsa", [UIColorredColor]),
mt_ShadowStyleMake(0.6,2, [UIColorblueColor],CGSizeZero),
mt_jianBianStyleMake([UIColor yellowColor], [UIColor purpleColor], CGPointMake(0, 0), CGPointMake(0, 100), CGSizeMake(100, 100)),
[UIImageimageNamed:@"sadsa"],
@(kSelected),
...............
);
mt_content(...) 为一个宏定义,里边的样式属性数量是可变的,通过向这个宏定义传递基本的样式属性(文字、图片、边框、view的状态等)创建一个 MTBaseViewContentModel 样式模型对象;实际使用中,传递的样式属性,会根据 view 有无这个样式属性去赋值,并不是会将模型传递的所有属性都赋值给View,如将带有图片的样式模型赋值给一个 UILabel 是不现实的,因为 Label 并不支持显示图片。
需要注意的是,原子样式属性 的优先级是最高的,如上面例子中 文本 @“text”,和 wordStyle中的 @“asdsa” 都是 为控件的文字属性赋值,但是最后显示的是 原子样式属性 @“text”。
MTBaseViewContentStateModel 使用:
MTBaseViewContentStateModel 是 MTBaseViewContentModel 的子类,在父类基础上增加对 View 状态的控制,并通过属性 viewState 去控制,使用如下:
mt_stateContent(
@(kSelected),
@"default",
mt_selectedContent(@"selected", ......)
)
1、用法同父类, 上例中,viewState 状态为 kSelected,所以为控件设置属性时,优先找 是否 存在 selected 属性的对象,selected 对象为 MTBaseViewContentStateModel 的属性,它也是一个 MTBaseViewContentModel,上例中 mt_selectedContent 中的内容实质上是为 selected 对象赋值。因此,如果存在 selected 对象,则会去找 selected 对象的样式属性进行设置,没有再找默认的设置。
2、样式设置都是一次性的,如果中途需要改变view的状态,需要重新赋值新状态的样式模型。
3、 mt_selectedContent(...) 等价于 mt_selected(mt_content(...)),因为比较常用的缘故所以设置简便写法。实际上 是通过 mt_selected(MTBaseViewContentModel) 方法包装样式对象,表明它是作为一个 MTBaseViewContentStateModel 对象的属性 selected。
4、状态模型的寻找是无限层级的,如:
mt_stateContent(
@(kSelected),
@"default",
mt_textColor([UIColor blackColor]),
mt_selected(
mt_stateContent(
@"selected",
mt_selectedContent(@"selected2")
)
)
);
上述这个模型最终赋值是 selected2,原因:由于状态是 kSelected,所以会去 selected属性找样式,发现 selected 属性也是一个 MTBaseViewContentStateModel对象,就会去找 selected 属性的 selected属性,最终找到 mt_selectedContent 对象,由于是一个 MTBaseViewContentModel 无法再往下找了,所以就取该 model 的text属性 @“selected2”;再找文本颜色,由于没有文本颜色,就去父层找,父层也没有,找到最顶层 文本颜色 mt_textColor,所以最后出来的文本颜色是 [UIColor blackColor]。
寻找原理如下:
1、父model -> n 个子model -> 最终子model,有则返回 子model 样式属性,无则进入步骤2;
2、最终子model -> n个父model -> 最终父model,有则返回 父model 样式属性,无则不设置。
实际使用多为2个层级,没有出现过再往2层包3层的情况。
5、状态样式,其实就是用一个 MTBaseViewContentStateModel 再包裹 MTBaseViewContentModel,所以使用时用 mt_stateContent 而不是 mt_content 去包裹。
6、以下列出 MTBaseViewContentStateModel 包含的用于设置不同状态的属性以及对应的包裹方法,以及对应 viewState:
1、selected -> mt_selected(MTBaseViewContentModel) 或:selected -> mt_selectedContent(样式属性s) ,viewState -> kSelected
2、highlighted -> mt_highlighted(MTBaseViewContentModel) 或:highlighted -> mt_highlightedContent(样式属性s) ,viewState -> kHighlighted
3、placeholder ->mt_placeholder(MTBaseViewContentModel) 或:placeholder -> mt_placeholderContent(样式属性s),viewState -> kPlaceholder
4、disabled ->mt_disabled(MTBaseViewContentModel) 或:disabled -> mt_disabledContent(样式属性s),viewState -> kDisabled
5、header ->mt_header(MTBaseViewContentModel) 或:header -> mt_headerContent(样式属性s),viewState -> kHeader
6、footer ->mt_footer(MTBaseViewContentModel) 或:footer -> mt_footerContent(样式属性s),viewState -> kFooter
一般不需要包裹第三层的话,直接用 mt_XXXContent(样式属性s) 的方式去设置属性更便捷。
简单总结:
1、mt_content(...) 创建的为一个 MTBaseViewContentModel 对象。
2、mt_stateContent(...) 创建的为一个 MTBaseViewContentStateModel 对象,如果你的控件样式展示涉及状态变化,请使用它,否则,通过 mt_content(...) 创建 MTBaseViewContentModel 即可。
3、MTBaseViewContentStateModel 的状态对象并不是必须的,实际上 selected、highlighted 等根据实际开发情况,如对于 UIButton 控件这些状态量是有明显区别,但其他控件则没有,所以除开按钮控件,其实这些 状态对象 是一样的,你可以给它辅 selected ,也可以是 highlighted、disabled等,随你喜欢。
4、默认 MTBaseViewContentModel 对象 viewState 为 kDefault。
MTViewContentModel 使用:
MTViewContentModel 是 MTBaseViewContentStateModel 的子类,实际上它代表的是包含多个子view 的一个自定义view 的抽象模型,它在之后讲解的 MTBaseCell 中有重要作用。它扩展的属性都是代表具体 子view 的 MTBaseViewContentModel 对象,来看看示例:
MTViewContentModel* viewContentModel =MTViewContentModel.new;
viewContentModel.mtTitle=mt_content(@"");
viewContentModel.mtContent=mt_content(@"123213");
viewContentModel.mtContent2=mt_content(@"2132");
上例中,为 MTViewContentModel 内的控件样式模型 mtTitle 创建 MTBaseViewContentModel,对应地,mtContent、mtContent2 也创建 MTBaseViewContentModel。
1、对于 UILabel,对应 viewContentModel 的 mtTitle、mtContent ~ mtContent8 属性;
2、对于 UIImageView,对应 viewContentModel 的 mtImg ~ mtImg9 属性;
3、对于 UIButton,对应 viewContentModel 的 mtBtnTitle ~ mtBtnTitle9 属性;
4、对于 UITextField 和 UITextView,对应 viewContentModel 的 mtTextField 和 mtTextView 属性;
5、如果你的view 中 还包含以上 属性不能涵盖的 view,则可根据情况构造数据,对应的是 viewContentModel 中的 mtExternContent 属性,它是一个 NSObject 类型的属性,用于处理不在以上范围的数据情况。
至此,通过完成一个 MTViewContentModel 模型的构造,相应的 view 对应的样式设置已经完成,接下来的只是将 MTViewContentModel 传递给 自定义view,再注意分发对应属性的 MTBaseViewContentModel 对象 到 自定义view 的子view中,即完成了自定义view 的样式设置。这个自定义view 就是一下讲到的 MTBaseCell。
外层躯壳:MTBaseCell 的使用
继承自 MTDelegateTableViewCell、MTDelegateCollectionViewCell、MTDelegateHeaderFooterView、MTDelegateCollectionReusableView、MTDelegateView,用于作样式快速搭建使用的子类:MTBaseTableViewCell、MTBaseCollectionViewCell、MTBaseHeaderFooterView、MTBaseCollectionReusableView、MTBaseView,统一用于作为承载 MTBaseViewContentModel 的样式容器,以下将这些控件统称为 MTBaseCell。
实质上这些容器 内部已经定义好所有对应 MTViewContentModel 的子控件,只是以懒加载的形式按需添加至容器上。
以下以 MTBaseCollectionViewCell 为示例进行讲解,其余 样式 view 中的子控件含义与 MTBaseCollectionViewCell 相同,实现原理也相同,只是控件数目上有所差别。
1、MTBaseCollectionViewCell 包含 4 组(UILabel、UIImageView、UIButton),包括 1 组(UITextField、UITextView)和 1 个 扩展用的 UIView。
2、MTBaseSubCollectionViewCell 继承 MTBaseCollectionViewCell,在此基础上再扩展 1组(UILabel、UIImageView、UIButton)。
3、MTBaseSubCollectionViewCell2 继承 MTBaseSubCollectionViewCell,在此基础上再扩展 1组(UILabel、UIImageView、UIButton)。
4、MTBaseSubCollectionViewCell3 继承 MTBaseSubCollectionViewCell,在此基础上再扩展 3组(UILabel、UIImageView、UIButton)。
这样做的目的,是为了省却平时自定义cell 时需要重复去定义各种UI控件的问题,过于繁琐,很多时候它们只是命名不同,实际上毫无区别,还不如在父类定义足够多的子控件,需要时按需加载添加到父控件上即可,我们的自定义view只要用来设置样式就可以了,没必要再去写控件定义这些无谓的操作。
MTViewContentModel 样式属性 与 MTBaseCollectionViewCell 控件 对应关系如下(同样适用其余 baseCell):
mtTitle -> textLabel,
(mtContent ~ mtContent8) -> (detailTextLabel ~ detailTextLabel8),
(mtImg ~ mtImg9) -> (imageView ~ imageView9),
(mtBtnTitle ~ mtBtnTitle9) -> (button ~ button9),
mtTextField -> textField,
mtTextView -> textView,
mtExternContent -> externView
通过这种映射关系,将抽象的控件样式 设置成 真实的控件样式。
通过 MTBaseViewContentModel 为 基本控件 设置样式
以下示例代码是等价的:
self.textLabel.baseContentModel = mt_content(@"text"); //传统写法
self.textLabel.objects(mt_content(@"text")); //进阶写法
self.textLabel.viewContent(@"text"); //常用写法
实际上,设置样式的本质,就是为控件的 baseContentModel 属性 赋值。这是一个 MTBaseViewContentModel 类型的属性,通过运行时添加。
前面已经说过 可以通过 mt_content(...) 便捷创建一个样式模型,创建完成再赋值给 控件的 baseContentModel 属性,内部即会完成控件相关样式属性的设置。
上面最后两个写法是通过宏实现的,原理也是赋值给 baseContentModel 属性。
对于最后一种写法,.viewContent(...) 默认创建的是 MTBaseViewContentModel,如果需要赋值 MTBaseViewContentStateModel,可以使用 .viewStateContent(...)。
通过 MTViewContentModel 为 MTBaseCell 设置样式
这里暂时讲述直接新建出 MTBaseCell 的用法,对于作为 ListView 的cell、组头、组尾的用法在下一章节讲述。
以下示例代码是等价的:
MTViewContentModel* viewContentModel = MTViewContentModel.new;
baseCell.contentModel = viewContentModel; //传统写法
baseCell.objects(contentModel); //进阶写法
baseCell.objects(@{}); //常用写法
创建好 viewContentModel,按需为对应的 控件属性赋值,在为 baseCell 设置样式的时候,会判断 viewContentModel 有无对应控件的 样式属性,有则创建该控件并添加至 baseCell 上,最后,通过上一小节将的 基本控件样式设置,逐一为子控件进行样式设置。
需要注意的是,可以通过传入 字典对象代替 MTViewContentModel 对象,内部会自动将你的字典对象转换成 模型 。其中,字典对象 与 MTViewContentModel 对象 和 baseCell 对象 的属性对应关系如下:
kTitle -> mtTitle -> textLabel,
(kContent ~ kContent8) -> (mtContent ~ mtContent8) -> (detailTextLabel ~ detailTextLabel8),
(kImg ~ kImg9) -> (mtImg ~ mtImg9) -> (imageView ~ imageView9),
(kBtnTitle ~ kBtnTitle9) -> (mtBtnTitle ~ mtBtnTitle9) -> (button ~ button9),
kTextField -> mtTextField -> textField,
kTextView -> mtTextView -> textView,
kExternContent -> mtExternContent -> externView。
如用常用写法 为 baseCell 设置样式,写法如下:
baseCell.objects(@{
kTitle:mt_content(@"text"),
kImg:mt_content(@"img"),
kBtnTitle : mt_stateContent(@"btnText")
});
以上设置会为 baseCell 的 textLabel、imageView、button 设置样式。
样式快速搭建框架 + 列表快速搭建框架 的联合使用
通过以上例子,已经知道可以通过 字典的形式去设置 baseCell的样式,当将 baseCell 运用到列表快速搭建框架中时,一切就变的简单多了,示例如下:
@[
@{
kTitle:mt_content(@"text"),
kImg:mt_content(@"img"),
kBtnTitle:mt_content(@"btnText"),
}
]
.bind(@"MTBaseCollectionViewCell")
.automaticDimension()
.bindClick(^(NSIndexPath* indexPath) {
if([indexPath.mt_order isEqualToString:kBtnTitle])
NSLog(@"哈哈哈");
});
1、定义了一组列表数据,里面包含一个字典对象,绑定了 MTBaseCollectionViewCell,内部机制会将这个字典 转化成 MTViewContentModel,分别对 button、label、imageView 进行样式设置。
2、绑定了点击事件,通过获取 传递回来得 indexPath 的 mt_order 属性,获取对应字典对象 的key 值,这个值就判断是 哪个控件做出的点击。mt_order 没有值则代表是 cell 本身的点击。
3、cell、组头、组尾的样式设置原理 和上面的例子是一致的。
统一样式管理者:MTViewContentManager 的使用
关于默认样式模型
当使用 baseCell 显示列表数据时,由于设置样式的不确定性,有可能存在同一个 cell中,同一子控件 设置相应的样式属性,在 cell 进行复用的时候 ,这个子控件又设置了另外的样式属性,但是这时还残留上一个样式模型的样式属性,这就导致这个子控件显示出来时将这两种不相关同时显示了,这就是需要默认样式模型的原因。
通常的做法是在自定义 cell 中,先为子控件赋值一个默认的样式模型,然后再外头构造数据时,再传入想设置的样式,示例如下:
-(void)setupDefault
{
[super setupDefault];
self.textLabel.defaultViewContent(
mt_WordStyleMake(18,@"defaultText", kColor_2e2e2e)
.horizontalAlignment(NSTextAlignmentCenter)
.bold(YES),
);
self.button.defaultViewContent(
mt_WordStyleMake(18,@"btnText", kColor_F41611)
);
}
用法同上面讲解过的 通过 MTBaseViewContentModel 为 基本控件 设置样式,在前面多加了个 default,实际上是给 MTBaseViewContentModel 的 beDefault 属性赋值,用于标记 这个模型为默认样式模型。
对于 MTBaseViewContentModel,先在自身查找有无对应样式属性,有则使用,无则查找默认模型的使用。
对于 MTBaseViewContentStateModel,样式设置的完整寻找原理为:
1、父model -> n 个子model -> 最终子model,有则返回 子model 样式属性,无则进入步骤2;
2、最终子model -> n个父model -> 最终父model,有则返回 父model 样式属性,无则查找默认模型的使用。
这样,在外头构造这个列表数据时,我们可以缺省,传不同类型的样式属性,如:
@[
@{
kTitle:mt_content(@"text"),
} ,
@{
kTitle:mt_content(mt_textColor([UIColor blackColor])),
}
]
.bind(@"MTBaseCollectionViewCell")
.automaticDimension()
里头有两个 cell,一个设置文本,一个设置文本颜色,如果同一个cell复用,设置了这两个数据,并不会显示一个 黑色的 text 文本,因为前面设置了默认模型,所以这个 cell 只会显示 kColor_2e2e2e 的 text 或者 [UIColor blackColor] 的 defaultText。
MTViewContentManager 的使用
使用 样式管理者的目的,是提高样式的复用度,虽然上面所说的样式设置写法已经很方便,但是对样式的复用性却不大,如果有 100 个 控件使用 相同的 wordStyle、或borderStyle,那就要写 100次 wordStyle、borderStyle。
实际上对于常用的一些样式,除文本外,诸如字号,字体颜色、边框、阴影、背景色等,完全可以写在统一的地方,完全不再需要在去重复写相同的 wordMake、borderMake等。
创建样式管理者,步骤如下:
1、创建一个样式管理类,继承 MTViewContentManager,并使用
RegisterContentManager(className) 注册该样式管理类。如:
@interface ViewContentManager : MTViewContentManager @end
RegisterContentManager(ViewContentManager)
2、定义返回包含所需样式的数组。如:
@interface ViewContentManager : MTViewContentManager
@property (nonatomic,strong, readonly) NSArray* colorF4F4F4_17pt_Bold;
@end
@implementation QXViewContentManager
createArray(colorF4F4F4_17pt_Bold,
mt_WordStyleMake(17,nil,kColor_F4F4F4)
.bold(YES)
.lineBreakMode(NSLineBreakByTruncatingTail)
)
@end
1、通过宏 createArray(property, ...) 快速创建样式属性的懒加载,上例中添加了一个 wordStyle 对象,可以根据情况再接着添加其他样式,如在后面接着添加 borderStyle 对象,文本,背景色等。
2、关于属性名字的命名,根据具体的样式去命名属性,推荐顺序先对 文本(颜色、字号、对齐方式)描述,再是边框(线粗、圆角、颜色)描述,再是背景描述。每个原子样式间用下划线隔开,以下举几个例子:
@property (nonatomic,strong, readonly) NSArray* colorF4F4F4_17pt_Bold;
@property (nonatomic,strong, readonly) NSArray* r4_border1_F0F0F0;
@property (nonatomic,strong, readonly) NSArray* r8_border1_FF865E_bgFEF9F7;
@property (nonatomic,strong, readonly) NSArray* colorA7A7A7_12pt_lineSpacing6_row0;
@property (nonatomic,strong, readonly) NSArray* colorFF3F1A_12pt6_12ptBold_alignCenter_r4_border1_FF3F1A;
@property (nonatomic,strong, readonly) NSArray* colorF41510_12pt1_18pt_12pt3_fontBarlowMedium;
1、文本颜色以 colorXXXX 方式命名;文本字体 直接以 Xpt 的方式直接拼接在 colorXXXX 后,对于正常文本的情况,字体粗细可以 _X 直接拼接在 Xpt 后,如:12pt_Bold;对于富文本,若颜色相同,不同的字体大小间以 _ 连接,并在 Xpt 后添加 数字 指明有多少个字符是这个字号,如:12pt3_15pt_16pt4,文本粗细的添加方式,则改为直接拼接在字号后,如:12pt3Bold_15ptBold_16pt4;若颜色不同,只需在连接下一个字号之前先连接颜色即可。如:
colorWhite_12pt3Bold_colorRed15ptBold_16pt4,这个表明 只有 15ptBold 的字符才会是红色。
2、圆角以 rX 方式命名,X 代表圆角大小;
3、边框以 borderX 方式命名,X代表线粗; 边框颜色以 _XXXX 的方式直接拼接在 borderX 后;
4、背景以 bgXXXX命名;
5、对齐方式以 alignXXXX 命名;XXXX 用于标识你的对齐方式;
6、行距以 lineSpacingX 命名;X 代表行距;
7、行数以 rowX 命名;X 代表行数;
8、字体以 fontXXXX 命名;XXXX 代表字体;
通过样式管理者设置控件样式
同 .viewContent、.viewStateContent、.defaultViewContent、.defaultViewStateContent 一样,样式管理者设置样式也使用相同的方式,例子如下:
self.textLabel.wishContent(colorF41510_12pt_alignCenter,@"01天"); self.detailTextLabel.wishStateContent(colorF41510_12pt_alignCenter,@"23");
self.detailTextLabel2.wishDefaultContent(colorF41510_12pt_alignCenter,@"55"); self.detailTextLabel3.wishDefaultStateContent(colorF41510_12pt_alignCenter,@"30");
通过 .wishContent、.wishStateContent、.wishDefaultContent、.wishDefaultStateContent 为控件设置已经定义好的样式,与.viewContent() 的区别在于,.wishContent(),首先需要指定 样式管理者的一个属性,如上面统一使用 colorF41510_12pt_alignCenter 的属性样式,表明 label 字体颜色为 F41510,并且为居中显示的 12pt 字体,这几个 label 样式相同,不同的只是文本。试想如果不使用样式管理,直接使用 .viewContent,在这个例子就需要写 4个 wordStyleMake,而样式管理不仅简化了写法,还能提高样式复用减少这些重复的不必要的创建。同上面章节提到一样,加了 default 的样式设置是为控件提供默认模型。
使用总结
1、复合样式对象 mtWordStyle、mtBorderStyle、mtShadowStyle、mtJianBianStyle 的常用创建方式;
2、通过 mt_content(...)、mt_stateContent(...) 创建一个 MTBaseViewContentModel;
3、.viewContent(...)、.viewStateContent(...) 设置样式; .defaultViewContent(...)、.defaultViewStateContent(...) 设置默认样式;
4、通过样式管理,使用 .wishContent(...)、.wishStateContent(...) 设置样式;.wishDefaultContent(...)、.wishDefaultStateContent(...) 设置默认样式;
5、以字典的方式,构建 MTViewContentModel ,并能理解 字典中 key 与MTViewContentModel 属性 以及 baseCell 中的子控件 的一一对应关系;
6、通过 baseCell.objects(@{}) 的方式设置 baseCell 以及内部子控件的样式;
7、通过以 特定的 key 以及 value 为 MTBaseViewContentModel 的方式去构造列表数据,将样式框架与列表框架联合使用;
8、创建 列表使用cell 时存在 复用问题,需要在 初始化方法中,通过 .defaultViewContent(...)、.defaultViewStateContent(...) 或 .wishDefaultContent(...)、.wishDefaultStateContent(...) 指定 cell 的默认样式,避免重用时造成多个样式的复合;
9、样式管理类中样式的命名使用统一规则的命名方式,做到望文生义;通过宏 createArray(property, ...) 快速创建样式属性的懒加载;
10、样式状态 viewState 是在一开始就定好,中途即使改变,样式也不会改变,它的用途这是在刚开始设置样式时用于寻找复合条件的 MTBaseViewContentModel。
最后说两句
至此,关于样式设置快速搭建的内容已讲解完毕,想了解具体原理,文字是很难表达出来,只有看过源码,才明白这样做的理由,这里只是简单讲解框架的大概以及一些常用的使用方式,一些常用设置的样式属性并未提及,其实有 思维导图可能帮助你更好的理解,有空的话本人会尝试补充整个框架的概要图,封装的过程是艰辛的,但使用起来是方便的,可能每个人对编码的理解不同,但这就是我认为的最好的做法,希望对你的 iOS 开发有启发!
理解这套机制,可参考这些库模块内容:
MTBaseCell 模块、MTViewContentModel 模块
以及 文章 UITableView、UICollectionView列表控件的快速搭建 最后所提及的模块。
建议先看 上面链接的文章 理解 列表快速搭建框架,再来看本文,这种样式搭建方法 除了做到便利以外,另一个很大的用途就是 配合 列表搭建框架 来做列表的 快速高效搭建,两者配合使用 基本上秒杀 搭建 99% 的列表展示界面。