Demo写来一时爽,配套解说火葬场啊……
Yoga简介
Facebook
出品的Yoga是一个实现了Flexbox布局方式的跨平台布局引擎。底层代码开头是用C语言写的,后来改用C++来写了。
Yoga
在iOS
平台上的实现叫做YogaKit
,然后FlexLayout
的作者Luc
给它套了一层,让它写起来更简洁直观,并且支持链式语法。
如果对它的性能有疑问,客官可以先看看Luc
做的布局引擎跑分测试项目,都说跑分是浮云,用户体验才是王道,我们简单看看结果好了:
当然啦,WWDC2018里,苹果爸爸说Autolayout
有巨大的提升,希望Luc
早日更新华山论剑结果。
基本Flexbox概念
这部分客官直接看看这篇文章就可以了,里面介绍得很详细,我也是从小看着它长大的,哦不对,是看着它入门的。
FlexLayout做了什么
下面的内容是基于你已经基本领悟了上节提到的文章的情况下写的,不想重复提太多基本概念。 如果你发现本节内容看不下去,那你可以先跳过,反正你会回来看的(或者你会自己看源码)。
简单聊聊FlexLayout
,你看上篇的代码里到处都是flex
字眼,它是个什么东西呢?
public extension UIView {
public var flex: Flex {
if let flex = objc_getAssociatedObject(self, &flexLayoutAssociatedObjectHandle) as? Flex {
return flex
} else {
let flex = Flex(view: self)
objc_setAssociatedObject(self, &flexLayoutAssociatedObjectHandle, flex, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return flex
}
}
}
复制代码
用了大家喜闻乐见的Runtime
关联属性,给UIView
增加了一个叫flex
的属性,有的话直接返回,没有的话,立即创建一个再返回,初始化方法传了view本身进去:
public private(set) weak var view: UIView?
private let yoga: YGLayout
init(view: UIView) {
self.view = view
self.yoga = view.yoga
// Enable flexbox and overwrite Yoga default values.
yoga.isEnabled = true
}
复制代码
很简单的代码,view
强引用flex
,flex
对象弱引用view
,调用YogaKit
的方法开启Flexbox
布局。
然后是这个常用到的方法:
public func define(_ closure: (_ flex: Flex) -> Void) -> Flex {
closure(self)
return self
}
复制代码
传进来一个闭包,闭包要求有一个Flex对象做参数给它,然后执行这个闭包,把自己(flex)传给闭包,然后返回自己(flex)。简单来说就是为了让代码具有缩进层次,阅读起来更具辨识度。
?还要特别注意的是这两个方法:
// 1
public func addItem() -> Flex {
let view = UIView()
return addItem(view)
}
// 2
public func addItem(_ view: UIView) -> Flex {
if let host = self.view {
host.addSubview(view)
return view.flex
} else {
preconditionFailure("Trying to modify deallocated host view")
}
}
复制代码
先看2
,把参数view
添加到flex
所属的view
,作为它的子view,然后返回子view的flex
对象,换言之,a.addItem(b).marginTop(10)
的效果是把b
作为a
的subview,然后设置b
的顶部外边距为10
。
再看1
,不传参数的话,新建一个UIView
并传给2
的方法,换言之,a.addItem().height(1)
的效果是添加一个view到a
里,这个view的高度是1,匿名,除了a
的subviews
数组,没有其他引用可以访问到这个view
了。
Flex类
剩余的其他扩展方法基本上都是转化为操作YogaKit
的代码,让你用得省心,写得舒心。
这么说来可能很抽象,不过不要紧,下面我们就开始真枪实弹搞对象。
布局基础演示
布局方向 - direction
假设我们现在随便放蓝、绿、洋红三个UIView
,代码如下:
import UIKit
import FlexLayout
class DemonstrationVC: UIViewController {
fileprivate var rootFlex = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.96, alpha: 1)
view.addSubview(rootFlex)
let blueView = UIView()
blueView.backgroundColor = .blue
let greenView = UIView()
greenView.backgroundColor = .green
let magentaView = UIView()
magentaView.backgroundColor = .magenta
// 1
rootFlex.flex.addItem(blueView).width(100).height(50)
rootFlex.flex.addItem(greenView).width(100).height(50)
rootFlex.flex.addItem(magentaView).width(100).height(50)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
rootFlex.frame = view.bounds
rootFlex.flex.layout()
}
}
复制代码
上面的代码我只粘贴这一次了,为了节省篇幅,后文的讲解基本只展示布局所需的核心代码。看看效果:
Inspector看看层级:
可以看到没有避开导航栏,子view是从父view(rootFlex)的左上角开始布局的,方向是纵向,一个view挨着一个排列。
因为Yoga
在移动端是【以纵向作为主轴(main-axis)】的,相当于rootFlex
默认设置了.direction(.column)
了,下面我们来试试改变主轴为横向,并且粗暴地避开导航栏,在// 1
下面插入两行再Command + R
:
rootFlex.flex.direction(.row)
rootFlex.flex.marginTop(64)
复制代码
可以看到rootFlex
的Y
值已经是64
了,或许你已经注意到了,我从未提及的那一小撮代码:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
rootFlex.frame = view.bounds
rootFlex.flex.layout()
}
复制代码
简单来说就是,有行为引起viewDidLayoutSubviews
执行的时候,rootFlex
把自己设置成和self.view
一样大小,然后调用FlexLayout
的layout(mode:)
方法布局。
这个方法会根据你之前曾经设置给flex
的所有属性(外边距,宽度,高度等),按参数mode
指定的方式开始计算布局,调整view的frame。
参数mode
后面再说,一步步来嘛。
说好的链式调用呢?下面来改成等价的写法:
// rootFlex.flex.direction(.row)
// rootFlex.flex.marginTop(64)
// rootFlex.flex.addItem(blueView).width(100).height(50)
// rootFlex.flex.addItem(greenView).width(100).height(50)
// rootFlex.flex.addItem(magentaView).width(100).height(50)
rootFlex.flex.direction(.row).marginTop(64).define { flex in
flex.addItem(blueView).width(100).height(50)
flex.addItem(greenView).width(100).height(50)
flex.addItem(magentaView).width(100).height(50)
}
复制代码
有没有觉得多年白内障突然一觉醒来就视力5.2了?
唔,夸张了点,但是这样写很明显容易理解:rootFlex的主轴方向是横向,rootFlex顶部外边距是64,它添加了3个子view……
主轴对齐方式 - justifyContent
现在我接到新需求,需要让这三个view靠屏幕右边,一个挨着一个,怎么办呢?
因为我们的rootFlex
主轴布局方向是.row
即横向的,要修改主轴的item
对齐方式就要用.justifyContent()
来修改:
rootFlex.flex.direction(.row).justifyContent(.end).marginTop(64).define { flex in
flex.addItem(blueView).width(100).height(50)
flex.addItem(greenView).width(100).height(50)
flex.addItem(magentaView).width(100).height(50)
}
复制代码
如果不设置justifyContent
,那么它默认是.start
的,其他的模式你可以对着文档自行试试就明白了。
侧轴(cross-axis)对齐方式 - alignItems
侧轴就是和主轴90°交叉的那个轴,rootFlex
主轴是横向****,那么它的侧轴就是纵向,通过.alignItems()
来设置,默认是.stretch
(拉伸)的,啥意思?我们去掉三个view的侧轴方向的尺寸试试(这里就是纵向的尺寸,即高度啦):
rootFlex.flex.direction(.row).justifyContent(.end).marginTop(64).define { flex in
flex.addItem(blueView).width(100)
flex.addItem(greenView).width(100)
flex.addItem(magentaView).width(100)
}
复制代码
wow,好暴力……要注意的是,baseline
模式是废的,Yoga
并没有实现它,所以嘛,憧憬一下就好了。
问题又来了,我们的绿view要放头顶,哦,你们的,我头顶不要绿……
rootFlex.flex.direction(.row).justifyContent(.end).marginTop(64).define { flex in
flex.addItem(blueView).width(100)
flex.addItem(greenView).width(100).height(50)
flex.addItem(magentaView).width(100)
}
复制代码
高度的优先级会比侧轴要高,你明确指定了高度,它就不会拉长你了:
试试其他值,唔,让你们中间绿:
rootFlex.flex.direction(.row).justifyContent(.end).alignItems(.center)
.marginTop(64).define { flex in
flex.addItem(blueView).width(100)
flex.addItem(greenView).width(100).height(50)
flex.addItem(magentaView).width(100)
}
复制代码
哎呀,怎么只有一片绿,不见了其他两个小伙伴?因为你没设置他们的高度呀……重新设置他们的高度为80
,不贴代码了,看疗效:
唔,这时候我还是想绿在头顶怎么办?可以用自己的.alignSelf()
来覆盖父盒子的.alignItems()
设置:
rootFlex.flex.direction(.row).justifyContent(.end).alignItems(.center)
.marginTop(64).define { flex in
flex.addItem(blueView).width(100).height(80)
flex.addItem(greenView).width(100).height(50).alignSelf(.start)
flex.addItem(magentaView).width(100).height(80).alignSelf(.end)
}
复制代码
?不错不错
--
自动换行 - wrap
先来点准备随机色的代码:
/// 搞些浅色的随机色
fileprivate extension UIColor {
static var random: UIColor {
return UIColor(red: randomFloat(), green: randomFloat(),
blue: randomFloat(), alpha: 1)
}
static func randomFloat() -> CGFloat {
return 1 - CGFloat(arc4random_uniform(200)) / 255.0
}
}
复制代码
然后我们来排布12
个按钮看看是什么效果:
// 生成12个按钮, 随机背景色
let views: [UIButton] = (0..<12).map{
let v = UIButton()
v.backgroundColor = .random
v.setTitle(String($0), for: .normal)
return v
}
rootFlex.flex.direction(.row).marginTop(64).define { flex in
for v in views {
flex.addItem(v).width(70).height(50)
}
}
复制代码
可以看到subview
超过了父viewrootFlex
的显示范围,想让它在剩余空间放不下下一个的情况下自动换行的话,可以用.wrap
:
rootFlex.flex.direction(.row).wrap(.wrap).marginTop(64).define { flex in
for v in views {
flex.addItem(v).width(70).height(50)
}
}
复制代码
当然你也可以用wrapReverse
,那么左上角的就是11
,10
……这样排列了。
唔,如果你觉得4
和9
右边留白太难看,可以考虑下主轴居中:
rootFlex.flex.direction(.row).justifyContent(.center).wrap(.wrap).marginTop(64).define { flex in
...
}
复制代码
你就会惊喜地发现它换了一种难看的方式。
可以在按钮之间加点间距(为了多出一个,我把前面数组改成13个按钮了):
rootFlex.flex.direction(.row).wrap(.wrap).marginTop(64).define { flex in
for v in views {
flex.addItem(v).width(70).height(50).marginRight(10).marginTop(10)
}
}
复制代码
当这种多行的情况你想控制内容整体的侧轴布局时,可以用.alignContent
试试:
rootFlex.flex.direction(.row).wrap(.wrap).alignContent(.spaceAround)
.marginTop(64).define { flex in
...
}
复制代码
其他值请自行测试咯。
增长和压缩权重 - grow, shrink
让我我们回到三点式做演示:
let viewA = UIView()
viewA.backgroundColor = .blue
let viewB = UIView()
viewB.backgroundColor = .green
let viewC = UIView()
viewC.backgroundColor = .magenta
rootFlex.flex.direction(.row).marginTop(64).define { flex in
flex.addItem(viewA).width(80).height(50)
flex.addItem(viewB).width(80).height(50)
flex.addItem(viewC).width(80).height(50)
}
复制代码
我想左右护法固定宽度,中间的绿自动获得父盒子的剩余空间,就要用到.grow
:
rootFlex.flex.direction(.row).marginTop(64).define { flex in
flex.addItem(viewA).width(80).height(50)
flex.addItem(viewB).height(50).grow(1)
flex.addItem(viewC).width(80).height(50)
}
复制代码
试试让洋红view也分一杯羹:
rootFlex.flex.direction(.row).marginTop(64).define { flex in
flex.addItem(viewA).width(80).height(50)
flex.addItem(viewB).height(50).grow(2)
flex.addItem(viewC).height(50).grow(1)
}
复制代码
除了蓝色的宽度之后,父盒子剩余的宽度分2份给绿色view,分1份给洋红view。
.shrink
的作用是在父盒子(superview)空间不足以放下所有子盒子(subview)的时候,如何压缩子盒子。假设我们把三个view的宽度都设置为150
,rootFlex
宽度只有375
,肯定是放不下最后一个的,现在设置当放不下的时候压缩绿色的宽度:
rootFlex.flex.direction(.row).marginTop(64).define { flex in
flex.addItem(viewA).width(150).height(50)
flex.addItem(viewB).width(150).height(50).shrink(1)
flex.addItem(viewC).width(150).height(50)
}
复制代码
布局实战 - 小说简介
上节说了一些基本概念,本节结合一个简单界面来演示一下如何用
FlexLayout
来布局。
如下图,这个是起点iOS端的小说介绍页(不会被告吧?),我们只用一部分来做布局演示,状态栏和导航栏我们忽略掉:
我个人的做法是,先在脑海里按盒子模型来给它的结构大概拆分成不同的盒子组合,我看起来是这样的:
灰色的就是背景图啦(实际上好像没有蔓延到淡红色的描述那里,后文再研究),洋红(紫色?)我画少了一条,别在意这种细节。
整体父盒子是白色,主轴为纵向,有两个子盒子,由子盒子撑开父盒子高度,据此可以写出核心代码如下:
import UIKit
import FlexLayout
class NovelSummaryVC: UIViewController {
fileprivate var rootFlex = UIView()
fileprivate var summaryView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.96, alpha: 1)
view.addSubview(rootFlex)
rootFlex.flex.marginTop(64).define { flex in
flex.addItem(summaryView).height(30%).backgroundColor(.white).define { flex in
}
flex.addItem().grow(1).backgroundColor(.lightGray)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
rootFlex.frame = view.bounds
rootFlex.flex.layout()
}
}
复制代码
我们的目标是白色的summaryView
,由于它还没有subview
来撑开它的大小,所以我先给它一个30%
来做做样子,不然你都看不见它啦。还有.backgroundColor()
也是很方便用于布局时候的调试,调整完成之后去掉就行啦。
接着我们来看看浅绿色的盒子:
- 它的主轴方向是横向的,两边距离父盒子外边距为
15
- 封面图片view应该是足以撑开整个浅色盒子的,宽高比例测量出来是
170:230
,宽度占父盒子的24.6%
(170/690),高度不必指定,设定宽高比之后会自动计算出来,与蓝色view的间距是22
- 除了封面图片view(和22间距)之后,剩余空间就是蓝色view的,蓝色view的高度假设一定能放得下5个洋红盒子
// VC添加一个属性
fileprivate let coverImgV = UIImageView()
// 布局代码
rootFlex.flex.marginTop(64).define { flex in
flex.addItem(summaryView).height(30%).backgroundColor(.white).define { flex in
// 浅绿色盒子, 横向排列子盒子, 距离父盒子(summaryView)左右外边距为15
flex.addItem().direction(.row).marginHorizontal(15).backgroundColor(.green).define { flex in
// 封面图, 右外边距22
flex.addItem(coverImgV).marginRight(22).width(24.6%).aspectRatio(17/23).backgroundColor(.lightGray)
// 浅蓝色盒子
flex.addItem().grow(1).shrink(1).backgroundColor(.blue)
}
}
flex.addItem().grow(1).backgroundColor(.lightGray)
}
复制代码
唔,模拟器尺寸和设计图似乎很像样了,对了,我发现上面的示意图确实需要修正,背景图不应该覆盖到浅红色view的。接下来搞浅红色的描述view:
- 它顶部距离浅绿色view大概12
- 内部有一个UILabel,单行,超长就截断,文字居中,顶部底部距离父盒子顶部还有点边距,直接设置父盒子的内边距(padding)处理
先来搞一个UILabel
,顺便把封面图片搞上,免得一坨坨颜色那么难看,不敢用人家封面,免得侵权呀,自己搞一个。
// VC增加属性
fileprivate let descLabel = UILabel()
coverImgV.image = UIImage(named: "novel_cover")
descLabel.font = UIFont.systemFont(ofSize: 16, weight: .medium)
descLabel.text = "“玄门羽衣白云心,一琴一剑一丹青”"
descLabel.textAlignment = .center
descLabel.textColor = UIColor(0.77, 0.64, 0.48, 1)
rootFlex.flex.marginTop(64).define { flex in
flex.addItem(summaryView).height(30%).backgroundColor(.white).define { flex in
// 浅绿色盒子, 横向排列子盒子, 距离父盒子(summaryView)左右外边距为15
flex.addItem().direction(.row).marginHorizontal(15).backgroundColor(.green).define { flex in
// 封面图, 右外边距22
flex.addItem(coverImgV).marginRight(22).width(24.6%).aspectRatio(17/23)
// 浅蓝色盒子
flex.addItem().grow(1).shrink(1).backgroundColor(.blue)
}
// 浅红色盒子, padding(上, 左, 下, 右)
flex.addItem().marginTop(12).padding(12, 10, 8, 10)
.backgroundColor(UIColor(white: 0.9, alpha: 1)).define { flex in
flex.addItem(descLabel)
}
}
flex.addItem().grow(1).backgroundColor(.lightGray)
}
复制代码
行了,我知道封面丑。
这个时候我们summaryView
盒子的高度已经可以推算出来了,现在把height(30%)
去掉可以了,顺便那个绿底色也可以干掉了,辣眼睛:
再看蓝色view:
- 5个subview,主轴方向为
.row
,等间距分布对齐为.spaceBetween
- 星星什么的我就懒得搞了,找个图糊弄一下
先定义一下相关的view先(真不想写这种多余的注释):
/// 书名
fileprivate let bookNameLabel = UILabel()
/// 作者
fileprivate let authorLabel = UILabel()
/// 级别
fileprivate let levelLabel = UILabel()
/// 阅读量
fileprivate let readCountLabel = UILabel()
/// 分类
fileprivate let categoryLabel = UILabel()
/// 字数, 状态
fileprivate let wordCoundAndStateLabel = UILabel()
// 统一设置样式
fileprivate func configLabel(_ label: UILabel, text: String) {
label.text = text
label.font = UIFont.systemFont(ofSize: 12)
label.textColor = .white
}
复制代码
然后设置一下文本,添加到蓝色view里:
configLabel(bookNameLabel, text: "心魔")
bookNameLabel.font = UIFont.systemFont(ofSize: 16, weight: .medium)
configLabel(authorLabel, text: "沁纸花青")
levelLabel.text = "Lv4"
levelLabel.font = UIFont.systemFont(ofSize: 10)
levelLabel.textColor = UIColor(0, 0.8, 1, 1)
levelLabel.textAlignment = .center
levelLabel.layer.borderColor = UIColor(0, 0.8, 1, 1).cgColor
levelLabel.layer.borderWidth = 1
levelLabel.layer.cornerRadius = 4
levelLabel.layer.masksToBounds = true
configLabel(readCountLabel, text: "466万人读过")
configLabel(categoryLabel, text: "仙侠 | 幻想修真")
configLabel(wordCoundAndStateLabel, text: "266.0万字 | 连载")
rootFlex.flex.marginTop(64).define { flex in
flex.addItem(summaryView).backgroundColor(.white).define { flex in
// 浅绿色盒子, 横向排列子盒子, 距离父盒子(summaryView)左右外边距为15
flex.addItem().direction(.row).marginHorizontal(15).define { flex in
// 封面图, 右外边距22
flex.addItem(coverImgV).marginRight(22).width(24.6%).aspectRatio(17/23)
// 浅蓝色盒子
flex.addItem().justifyContent(.spaceBetween).grow(1).shrink(1).backgroundColor(.blue).define { flex in
flex.addItem(bookNameLabel)
flex.addItem(authorLabel)
flex.addItem(readCountLabel)
flex.addItem(categoryLabel)
flex.addItem(wordCoundAndStateLabel)
}
}
// 浅红色盒子, padding(上, 左, 下, 右)
flex.addItem().marginTop(12).padding(12, 10, 8, 10).backgroundColor(.black).define { flex in
flex.addItem(descLabel)
}
}
flex.addItem().grow(1).backgroundColor(.lightGray)
}
复制代码
好像还可以,蓝色太辣眼睛了,我们搞个背景图上来,然后干掉蓝色背景:
// VC增加一个属性
fileprivate let bgImgV = UIImageView(image: UIImage(named: "novel_bg"))
rootFlex.flex.marginTop(64).define { flex in
flex.addItem(summaryView).backgroundColor(.white).define { flex in
// 1
flex.addItem(bgImgV).position(.absolute).left(0).top(0).width(100%).height(100%)
// 浅绿色盒子, 横向排列子盒子, 距离父盒子(summaryView)左右外边距为15
flex.addItem().direction(.row).marginHorizontal(15).define { flex in
...(略)...
复制代码
哟呵,好像帅了不少,先来看看这个.position(.absolute)
是什么意思。
.position
的默认值是relative
,就是默认情况下你不写就是相对布局的意思啦。
这里我们给bgImgV
的布局方式明确指定为absolute
绝对布局,意思就是它的位置不再受限于上一个盒子,下一个盒子,而是相对于父盒子的左上角。
然后通过top()
、left()
,right()
,bottom()
这几个方法来设置自己的位置,我这里设置的就是左边和顶边距离父盒子为0
,然后宽度高度和父盒子一样。
唔,好像顶部太贴近导航栏了,给浅绿色盒子搞个顶部外边距就行了:
// 浅绿色盒子, 横向排列子盒子, 距离父盒子(summaryView)左右外边距为15
flex.addItem().direction(.row).marginTop(12).marginHorizontal(15).define { flex in
...(略)...
复制代码
顺眼多了,对了,还差名牌包包LV还没上呢,套一个盒子就好了:
// 浅蓝色盒子
flex.addItem().justifyContent(.spaceBetween).grow(1).shrink(1).define { flex in
flex.addItem(bookNameLabel)
flex.addItem().direction(.row).define { flex in
flex.addItem(authorLabel)
flex.addItem(levelLabel)
}
flex.addItem(readCountLabel)
flex.addItem(categoryLabel)
flex.addItem(wordCoundAndStateLabel)
}
复制代码
这个Lv有点挫呀……给左右加点内边距吧,还有左边也不要太靠近花青姐姐啦:
// 浅蓝色盒子
flex.addItem().justifyContent(.spaceBetween).grow(1).shrink(1).define { flex in
flex.addItem(bookNameLabel)
flex.addItem().direction(.row).define { flex in
flex.addItem(authorLabel)
flex.addItem(levelLabel).paddingHorizontal(6).marginLeft(8)
}
flex.addItem(readCountLabel)
flex.addItem(categoryLabel)
flex.addItem(wordCoundAndStateLabel)
}
复制代码
好像没那么挫了。要注意的是,Label
的textAlignment
要是.center
才显得出水平方向的padding
效果,不然增加的宽度全部在右边了。
好了,星星相信你也会搞了,懒得演示了。
下面我们来演示一下,有时候descLabel
不需要显示的情况。
先给浅红色盒子做一个变量来方便引用它:
// VC添加一个属性
fileprivate let descContainer = UIView()
// 浅红色盒子, padding(上, 左, 下, 右)
flex.addItem(descContainer).marginTop(12).padding(12, 10, 8, 10)
.backgroundColor(.black).define { flex in
flex.addItem(descLabel)
}
// viewDidLoad的最后添加
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.descContainer.flex.isIncludedInLayout = false
self.descContainer.isHidden = true
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
复制代码
哎哟,底部外边距没有了……因为是靠浅红色view的顶部外边距做的,现在我们改成浅绿色盒子的底部外边距吧:
// 浅绿色盒子, 横向排列子盒子, 距离父盒子(summaryView)左右外边距为15,顶底各12外边距
flex.addItem().direction(.row).marginVertical(12).marginHorizontal(15).define { flex in
...(略)...
}
// 浅红色盒子, padding(上, 左, 下, 右)
flex.addItem(descContainer).padding(12, 10, 8, 10)
...(略)...
复制代码
唔,可以了,好像少了点啥……封面右下角有个大V!
没事,我今天才摸索怎么用Sketch
,我给你画了个高清无码的,随便用:
// VC增加一个属性
fileprivate let verifiedImgV = UIImageView(image: UIImage(named: "novel_v"))
// 浅绿色盒子, 横向排列子盒子, 距离父盒子(summaryView)左右外边距为15,顶底各12外边距
flex.addItem().direction(.row).marginVertical(12).marginHorizontal(15).define { flex in
flex.addItem().marginRight(22).width(24.6%).aspectRatio(17/23).define { flex in
// 封面图, 右外边距22
flex.addItem(coverImgV)
flex.addItem(verifiedImgV).position(.absolute).right(-5).bottom(-5).size(20)
}
// 浅蓝色盒子
...(略)...
复制代码
原本的封面盒子现在用来装两个东西了,一个是封面view,一个是绝对定位的verifiedView
,宽高都是20,在右下角,超出superview了。
很简单的一个界面,啰啰嗦嗦废话多,希望小白都能看懂。
下一篇我们来讲讲layout(mode:)
的参数作用,以及如何结合到UIScrollView
使用。
文中用到的所有素材均为学习使用,请不要用于商业用途,否则后果说不定很严重,自负啊!