Bcaa9f0f4b2659bbf708be7cf03088a3
关于 Objective-C 版本的 SwiftUI 库的自动生成布局的研究心得

前言

最新突然突发奇想先做 Objective-C版本的SwiftUI这个仿生库 OCUI。发现其实困难还是很多的,比如基于Swift语法的语法特性可以让语法特别简洁,但是OC就显的十分的臃肿了。

  • 一个简单的横向文本居中

    HStack(^{
      Text(@"Hello World!");
    });

虽然居中大家都知道,可以用Masonry直接设置Center居中即可,但是却不适合其他的场景。

对于SwiftUI布局通常有下面四个

  • VStack

    纵向布局

  • HStack

    横向布局

  • ZStack

    垂直布局

  • Spacer

    空隙填充

目前我做的是横向布局HStackSpacer

布局框架示意图

image-20190829093331823

image-20190829093331823

布局基本上是按照上面图所示进行填充的。Spacer只在框架存在,在页面上已经变成布局的约束了。

这个布局思路,不知道从什么地方开始将,不如从 例子从简单到复杂的开始说起吧。

单个文本居中显示

HStack(^{
    Text(@"Hello World!");
});

image-20190829093912352

image-20190829093912352

image-20190829094101704

image-20190829094101704

其实看我们的代码里面是没有出现任何的Spacer布局的,我们可以在内部布局的时候如果缺失左右填充时候,可以进行添加。

我们此时生成布局方案的时候不能按照直接设置下面的伪代码

[label mas_makeConstraints:^(MASConstraintMaker *make) {
    make.center(superView);
}];

我们正常的会按照这样布局的,但是作为一个框架,不可能写无数个布局方案处理,应该有一个通用的布局方案。

既然左侧和右侧的Spacer的长度是按照中间显示文本的宽度自动计算的,那么我们可以在布局之前优先的算出Spacer的具体宽度。

CGFloat spacerWidth = (HStackWidth - LabelWidth) / 2;

我们先拿到父试图的宽度,目前按照会自动充满屏幕,就是屏幕的宽度ScreenWidth。因为UILabel是可以自动计算出合适大小的。我们可以使用UIViewintrinsicContentSize属性得到适合的宽度。

关于生成布局的思路可以把布局拆解出来

  • 布局上下的位置
  • 布局大小
  • 布局左侧或者右侧的位置

布局上下位置

对于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 张行!");
});

image-20190829112454402

image-20190829112454402

image-20190829112611122

image-20190829112611122

此时我们的代码仅仅是比上一个例子多了下面代码

top Created with Sketch.