前言
最新突然突发奇想先做 Objective-C
版本的SwiftUI
这个仿生库 OCUI。发现其实困难还是很多的,比如基于Swift
语法的语法特性可以让语法特别简洁,但是OC
就显的十分的臃肿了。
-
一个简单的横向文本居中
HStack(^{ Text(@"Hello World!"); });
虽然居中大家都知道,可以用Masonry
直接设置Center
居中即可,但是却不适合其他的场景。
对于SwiftUI
布局通常有下面四个
-
VStack
纵向布局
-
HStack
横向布局
-
ZStack
垂直布局
-
Spacer
空隙填充
目前我做的是横向布局HStack
和Spacer
。
布局框架示意图
布局基本上是按照上面图所示进行填充的。Spacer
只在框架存在,在页面上已经变成布局的约束了。
这个布局思路,不知道从什么地方开始将,不如从 例子从简单到复杂的开始说起吧。
单个文本居中显示
HStack(^{
Text(@"Hello World!");
});
其实看我们的代码里面是没有出现任何的Spacer
布局的,我们可以在内部布局的时候如果缺失左右填充时候,可以进行添加。
我们此时生成布局方案的时候不能按照直接设置下面的伪代码
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.center(superView);
}];
我们正常的会按照这样布局的,但是作为一个框架,不可能写无数个布局方案处理,应该有一个通用的布局方案。
既然左侧和右侧的Spacer
的长度是按照中间显示文本的宽度自动计算的,那么我们可以在布局之前优先的算出Spacer
的具体宽度。
CGFloat spacerWidth = (HStackWidth - LabelWidth) / 2;
我们先拿到父试图的宽度,目前按照会自动充满屏幕,就是屏幕的宽度ScreenWidth
。因为UILabel
是可以自动计算出合适大小的。我们可以使用UIView
的intrinsicContentSize
属性得到适合的宽度。
关于生成布局的思路可以把布局拆解出来
- 布局上下的位置
- 布局大小
- 布局左侧或者右侧的位置
布局上下位置
对于HStack
支持三种布局分别是下面
-
Top
-
Center
默认布局类型
-
Bottom
我们可以根据设置的三种类型进行上下布局
if (top) {
make.top.equal(superView);
} else if (center) {
make.centerY.equal(superView);
} else {
make.bottom.equal(superView);
}
关于大小布局
我们对于intrinsicContentSize
可以计算出大小的不进行布局,让系统自动约束。对于计算不出来的大小的UIView
可以单独调用renderSize
协议方法获取试图的大小。
如果renderView
的宽度为0
,就引出了自动约束试图大小的布局,这个下面讲述。宽度不为0
,我们可以设置下面约束
make.width.mas_equalTo(width);
如果renderView
的高度不存在,我们就设置为父试图的全部高度。
if (renderViewHeight > 0) {
make.height.mas_equalTo(renderViewHeight);
} else {
make.height.equal(superView);
}
关于左侧和右侧布局。
我的思路是这样的
- 如果和左侧的试图间距已知就按照左侧的进行布局
- 如果和左侧的试图间距可变就按照右侧已知间距的进行布局
- 如果和左侧的试图间距可变就按照右侧 如果右侧间距未知就按照右侧约束走
/// 是否存在左侧固定间距
if (leftSpacerFlxedOffset) {
if (isSuperView) {
make.leading.equeal(superView).offset(leftSpacerFlxedOffset.value)
} else {
make.leading.equeal(superView.mas_trailing).offset(leftSpacerFlxedOffset.value)
}
} else if (rightSpacerFlxedOffset) {
/// 是否存在右侧的固定间距
if (isSuperView) {
make.trailing.equeal(superView).offset(-leftSpacerFlxedOffset.value)
} else {
make.trailing.equeal(superView.mas_leading).offset(-leftSpacerFlxedOffset.value)
}
} else {
if (isSuperView) {
make.trailing.lessThanOrEqualTo(superView).offset(-leftSpacerFlxedOffset.value)
} else {
make.trailing.lessThanOrEqualTo(superView.mas_leading).offset(-leftSpacerFlxedOffset.value)
}
}
两个文本横向居中对齐
HStack(^{
Text(@"Hello World!");
Text(@"Hello 张行!");
});
此时我们的代码仅仅是比上一个例子多了下面代码
Text(@"Hello 张行!");
按照上面的布局思路,我们可以想象一下中间存在一个固定间隙为0
的Spacer
。按照这个思路,我们可以给Spacer
新增一个固定间隙的属性。从而可以实现下面的效果。
两个文本间隙10并且居中显示
HStack(^{
Text(@"Hello World!");
Spacer(@10);
Text(@"Hello 张行!");
});
两个文本左右对齐
HStack(^{
Text(@"Hello World!");
Spacer(nil);
Text(@"Hello 张行!");
});
我们可以设置最左侧和做右侧的Spacer
的固定间隙为0。
一个复杂一点的设置 Cell 布局
HStack(^{
Spacer(@15);
Image(nil)
.size(CGSizeMake(25, 25))
.backgroundColor([UIColor redColor]);
Spacer(@10);
Text(@"WIFI");
Spacer(nil);
Text(@"未连接");
Spacer(@15);
});
可能此时也已经发现了问题,当我设置左侧文本的时候,应该中间的Spacer
的间隙需要自动变小。但是其实事实上并没有。
因为开始的时候我们计算出了浮动间隙的具体值,我们设置了最小值。
目前的方案是做了KVO
的监听文本Text
的变化,从而重新计算间隙的大小,更新约束。
对于重新计算间隙的大小,还有一个复杂的逻辑。
四个文本左右间距为0 怎么让中间的间隙一直保持不变。
HStack(^{
Text(@"文本1");
Spacer(nil);
Text(@"文本2");
Spacer(nil);
Text(@"文本3");
Spacer(nil);
Text(@"文本4");
});
我们还存在一种情况,就是可能两个文本之间有最小间隙。
假设三个Spacer
的最小间隙分别是10
20
30
。
我们获取可以浮动的宽度就等于
CGFloat floatWidth = width - text1Width - text2Width - text3Width - text4Width;
我们可以算出间隙最小承受的值
CGFloat minFloatWidth = 10 + 20 + 30;
我们可以算出间隙最大的值
CGFloat maxFloatWidth = 30 * 3; /// MAX(10,20,30..) * count;
这个最大的值其实就是大于或者等于就可以把浮动的间隙平分。
if (floatWidth <= minFloatWidth) {
// 按照最小的间隙更新布局
} else if (floatWidth > minFloatWidth && minFloatWidth < maxFloatWidth) {
// 就按照优先级最低的按照最小间距布局 默认为最后一个优先级最小
} else {
// 平分计算进行更新布局
}
一个全部充满的红色试图
HStack(^{
View()
.backgroundColor([UIColor redColor]);
});
这个布局其实和刚才Spacer
的思路是一样的,但是存在不确定大小的试图,就不允许存在不确定间隙的Spacer
存在。
三个试图平分
HStack(^{
View()
.backgroundColor([UIColor redColor]);
View()
.backgroundColor([UIColor blueColor]);
View()
.backgroundColor([UIColor grayColor]);
});
三个试图其中一个设置具体大小
HStack(^{
View()
.backgroundColor([UIColor redColor]);
View()
.backgroundColor([UIColor blueColor])
.size(CGSizeMake(40, 40));
View()
.backgroundColor([UIColor grayColor]);
});
OCUI 正在测试开发阶段,Api 会尽量和
SwiftUI
保持一致。但是最后的 Api 按照最后发布为准。欢迎有志之士加入这个项目。