这里有一篇关于YogaKit使用方法的翻译文章,其中介绍的比较全面,基本可使用FlexBox布局,可是仅仅看这里的介绍仍是难以解决一些布局问题,本篇文章再也不介绍 FlexBox 和 YogaKit 的相关知识和概念,直奔主题实践,旨在经过案例帮助更好的解决问题。swift
在学习FlexBox布局的时候,为了加深理解建议学习一下CSS,理解盒子模型、外边距、内边距、定位、定位上下文及相对定位和绝对定位的区别,可以帮助咱们更好的理解FlexBox,更好的使用YogaKit。app
以下图,这种布局很常见,实现的方法也有不少,使用YogaKit实现也有不少方法,可是优雅程度不一样。布局
首先,使用 preservingOrigin 能够勉强实现这一布局,可是代码显得不规整,做为一种布局方法,我直接摒弃。post
let blueView = UIView(frame: .zero)
blueView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.marginTop = 60
}
view.addSubview(blueView)
let imageView = UIImageView(frame: .zero)
imageView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 100
layout.marginTop = 10
}
view.addSubview(imageView)
view.yoga.applyLayout(preservingOrigin: true)
imageView.yoga.applyLayout(preservingOrigin: false)
复制代码
其次,使用 margin 属性,将其赋负值也能够轻易实现此效果,然而这些都不是重点学习
let blueView = UIView(frame: .zero)
blueView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.marginTop = 60
}
view.addSubview(blueView)
let imageView = UIImageView(frame: .zero)
imageView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 100
layout.aspectRatio = 1
layout.marginTop = -50
layout.alignSelf = .center
}
blueView.addSubview(imageView)
view.yoga.applyLayout(preservingOrigin: true)
复制代码
问题1:YogaKit是布局流式布局的利器,在使用的时候会发现整个布局都是根据内容依次堆叠,在如图的布局样式中,若是要把 imageView 放到蓝色背景的底部,此时就没法使用 marginBottom 来完成。flex
解决这个问题首先想到的是经过调整父视图的内容布局属性来完成。spa
let blueView = UIView(frame: .zero)
blueView.backgroundColor = .blue
blueView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.marginTop = 60
layout.justifyContent = .flexEnd
}
view.addSubview(blueView)
复制代码
问题2:若是仅仅经过修改 layout.justifyContent = .flexEnd 来解决此问题就会引发新的问题,就是说会影响蓝色View中的其余内容,全部内容都以底部为起点开始布局,故此方案有部分影响,根据状况采纳。翻译
其次的解决方法能够是用定位来处理,只能说不要太完美。code
let blueView = UIView(frame: .zero)
blueView.backgroundColor = .blue
blueView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.marginTop = 60
layout.position = .relative
}
view.addSubview(blueView)
let image = UIImageView(frame: .zero)
image.backgroundColor = .yellow
image.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 100
layout.bottom = 0
layout.aspectRatio = 1
layout.alignSelf = .center
layout.position = .absolute
}
blueView.addSubview(image)
view.yoga.applyLayout(preservingOrigin: true)
复制代码
在项目中可能会遇到这种布局,使用YogaKit中的FlexWrap能更方便的解决item(图中圆角方块)换行问题,可是也存在一些问题,如图所示,为了便于说明作如下命名,图中深色圆角方块为 item,图中图片为 image,图中包裹 item 的边框为 wrapper,总体为 cellcdn
在此中样式的 cell 中,咱们但愿 image 按比例位于 cell 的最右边,因此 wrapper 的 flexGrow 设为 1,能够将 image 挤到最右边;wrapper 中的 item 要换行,因此 wrapper 的 flexWrap 设为 wrap,核心代码以下:
cell.configureLayout { (layout) in
layout.isEnabled = true
layout.flexDirection = .row
}
let wrapper = UIView(frame: .zero)
wrapper.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.flexDirection = .row
}
cell.addSubview()
let image = UIImageView(frame: .zero)
image.configureLayout { (layout) in
layout.isEnabled = true
layout.width = 140
layout.aspectRatio = 1
}
cell.addSubview(image)
for _ in 0..<8 {
let item = UILabel(frame: .zero)
item.configureLayout { (layout) in
layout.isEnabled = true
layout.marginHorizontal = 8
layout.marginVertical = 5
layout.width = 60
layout.height = 30
}
wrapper.addSubview(item)
}
复制代码
问题1:仅仅如此是不够的,运行会看到 image 被挤出屏幕外边,并且 item 们也没有折行,显然 wrapper 被内容撑开,未达到预期样式。
子视图会影响俯视图的大小,使用 flexWrap 属性可让子视图折行,可是前提是要给父视图一个明确的宽度。
let maxWidth = YGValue(view.bounds.size.width - 140)
wrapper.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.flexDirection = .row
layout.width = maxWidth
}
复制代码
问题2:在给定 wrapper 一个宽度后,貌似能够完美解决问题,可是前提是须要计算宽度,不够优雅,在一个宽度不固定的容器内显然不能使用此方法。
要解决此问题就要用到 position 属性,position 有两个值:.relative 相对定位 和 .absolute 绝对定位,相对定位能够做为绝对定位的定位上下文,决定绝对定位的参照物,若是没有定位上下文默认参照物为根视图。使用绝对定位使得该视图脱离布局流,位置相对于父视图,不会影响父视图大小。在 wrapper 中再添加一个 rapWrapper 来承载 item。
let wrapper = UIView(frame: .zero)
wrapper.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.position = .relative
}
cell.addSubview(wrapper)
let rapWrapper = UIView(frame: .zero)
rapWrapper.backgroundColor = .gray
rapWrapper.configureLayout { (layout) in
layout.isEnabled = true
layout.flexWrap = .wrap
layout.flexDirection = .row
layout.position = .absolute
}
wrapper.addSubview(rapWrapper)
复制代码
for _ in 0..<8 {
let item = UILabel(frame: .zero)
item.configureLayout { (layout) in
layout.isEnabled = true
layout.marginHorizontal = 8
layout.marginVertical = 5
layout.width = 60
layout.height = 30
}
rapWrapper.addSubview(item)
}
复制代码
一样是简单的布局,使用YogaKit也很简单,然而简单是要付出代价的,正确的使用才能避免没必要要的尴尬😓
看上去如此简单的布局,用YogaKit的时候会感觉到它的"魔性",前提是要正确的使用。
首先想到的布局方法是 scrollView 使用 flexGrow 充满,将 button 挤到底部,而 scrollView 的 contentSize 也不用计算了,在内部加一个 contentView 负责填充内容,撑开 scrollView 便可,简单到堪称完美。
let scrollView = UIScrollView(frame: .zero)
scrollView.backgroundColor = .blue
scrollView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
}
view.addSubview(scrollView)
let contentView = UIView(frame: .zero)
contentView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 300
}
scrollView.addSubview(contentView)
let button = UIView(frame: .zero)
button.backgroundColor = .yellow
button.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 50
}
view.addSubview(button)
view.yoga.applyLayout(preservingOrigin: true)
scrollView.contentSize.height = contentView.bounds.size.height
复制代码
问题1:看似很简单的布局,也为之后出错埋下了伏笔,当 contentView 的高度小时还看不出问题,可是当 contentView 的高度大于屏幕高度时,问题出现了,scrollView 不能滑动,button 也被挤到了屏幕外面。
contentView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 900
}
复制代码
如此,解决办法也就显而易见了,没错,使用定位解决问题。
let scrollView = UIScrollView(frame: .zero)
scrollView.backgroundColor = .blue
scrollView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.position = .relative
}
view.addSubview(scrollView)
let contentView = UIView(frame: .zero)
contentView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 900
layout.position = .absolute
}
scrollView.addSubview(contentView)
let button = UIView(frame: .zero)
button.backgroundColor = .yellow
button.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 50
}
view.addSubview(button)
view.yoga.applyLayout(preservingOrigin: true)
scrollView.contentSize.height = contentView.bounds.size.height
复制代码
感谢 yun1467723561418 这位好心朋友的指点,案例二和案例三中避免子视图撑大父视图用 flexShrink 一样能够解决,以下:
let wrapper = UIView(frame: .zero)
wrapper.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.flexShrink = 1
}
cell.addSubview(wrapper)
复制代码
for _ in 0..<8 {
let item = UILabel(frame: .zero)
item.configureLayout { (layout) in
layout.isEnabled = true
layout.marginHorizontal = 8
layout.marginVertical = 5
layout.width = 60
layout.height = 30
}
wrapper.addSubview(item)
}
复制代码
let scrollView = UIScrollView(frame: .zero)
scrollView.backgroundColor = .blue
scrollView.configureLayout { (layout) in
layout.isEnabled = true
layout.flexGrow = 1
layout.flexShrink = 1
}
view.addSubview(scrollView)
let contentView = UIView(frame: .zero)
contentView.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 900
}
scrollView.addSubview(contentView)
let button = UIView(frame: .zero)
button.backgroundColor = .yellow
button.configureLayout { (layout) in
layout.isEnabled = true
layout.height = 50
}
view.addSubview(button)
view.yoga.applyLayout(preservingOrigin: true)
scrollView.contentSize.height = contentView.bounds.size.height
复制代码
flexShrink 容许内容超出时缩小,标记内容超出时谁先缩小,优先级从根视图依次往下,如:A包含B,B包含C,在没有限制A的大小时A可能被内容撑开,设置B或者C的 flexShrink 是不起做用的,但设置A的 flexShrink能够保证B或C不会超出A的大小
不知道使用YogaKit的同窗有多少,也没有看到有介绍 position 的文章,我也是经过阅读了「CSS权威指南」后才尝试使用 position 的,同时对于 overflow 的使用仍是不得而知,但愿有懂的大神告诉我一下!有写的不对的地方,还请慷慨指出!