基于facebook/yoga实现一个类似swiftui和Flutter的声明式UI框架
- iOS 10.0+
- Xcode 12.5
- Swift 5.4
pod 'FlexLayoutKit', '~> 0.5'
以下可选
pod 'FlexLayoutKit/SDWebImage'
pod 'FlexLayoutKit/Kingfisher' #需要ios 12以上
- FlexBox布局
- 声明式语法,类似SwiftUI,如HStackView、VStackView、ZStackView,类似Flutter中的Row、Column、Stack、Wrap
- 自动计算UITableViewCell 高度
- 支持VScrollView、HScrollView,自动计算contentSize
- 使用Wrap轻松实现流式布局,超过屏幕时会自动换行
- Forin和if else DSL支持
- 数据驱动UI,更新数据后自动会更新UI
- 支持百分比
- 链式语法
import FlexLayoutKit //1.导入FlexLayoutKit
import UIKit
//2.继承FlexboxBaseViewController
class ViewController: FlexboxBaseViewController
{
override func viewDidLoad() {
super.viewDidLoad()
view.flex
.mainAxis(.center)
.crossAxis(.center)
.addItems(subviews: bodyView())
}
@FlexboxViewBuilder func bodyView() -> [FlexboxView] {
Text("Hello FlexLayoutKit")
}
}
or
import FlexLayoutKit
class ViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
view.flex.mainAxis(.center).crossAxis(.center).addItems {
HStackView(mainAxis: .center, crossAxis: .center) {
Text("Hello FlexLayoutKit")
}
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.flex.applyLayout()
}
}
HStackView {
ZStackView {
ImageView()
.backgroundColor(UIColor.gray.withAlphaComponent(0.5))
.cornerRadius(8)
.left(0)
.bottom(0)
.size(width: 50, height: 50)
Text("1")
.fontSize(12)
.textColor(.white)
.right(0)
.top(0)
.size(16)
.cornerRadius(8)
.backgroundColor(.red)
.textAlignment(.center)
}
.size(58)
.margin(.right, 8)
VStackView(mainAxis: .spaceAround) {
HStackView(crossAxis: .center) {
Text("Leo")
.fontSize(16, weight: .bold)
.expanded()
Text("13:30")
.fontSize(12, weight: .medium)
.textColor(.gray)
}
Text("hello,nice to meet you")
}
.height(50)
.expanded()
.margin(.top, 8)
}
.padding(.horizontal, 15)
.margin(.top, 100)
HStackView {
ImageView().size(40).cornerRadius(10).backgroundColor(.gray.withAlphaComponent(0.2))
Spacer(10)
Text("Leo").textColor(.orange).fontSize(16,weight: .medium)
}
VStackView(crossAxis: .center) {
ImageView().size(40).cornerRadius(10).backgroundColor(.gray.withAlphaComponent(0.2))
Spacer(10)
Text("Leo").textColor(.orange).fontSize(16,weight: .medium)
}
ZStackView {
FlexContainer(mainAxis: .center, crossAxis: .center){
Text("99")
}
.cornerRadius(15)
.backgroundColor(.red)
.top(0)
.right(0)
.size(30)
}
.size(100)
.backgroundColor(.orange)
let tags = ["tag1","tag2","tag3","tag4","tag5","tag6","tag7","tag8","tag9"]
//gap 是行间距和列间距简写
Wrap(gap: 10){
for tag in tags {
Text(tag)
.backgroundColor(.gray.withAlphaComponent(0.5))
.textAlignment(.center)
.cornerRadius(15)
.padding(.horizontal,10)
.height(30)
.onTap {
print(tag)
}
}
}
VScrollView {
for i in 0...100 {
FlexContainer(mainAxis: .center, crossAxis: .center) {
Text("\(i)")
}
.height(60)
.backgroundColor(.orange.withAlphaComponent(0.1))
.margin(.vertical,5)
}
}
let state = true
HStackView {
if state {
Text("true")
}else{
Text("false")
}
}
@UState var count: String = "count"
var step: Int = 0 {
didSet{
count = "count = \(step)"
}
}
VStackView(mainAxis: .center, crossAxis: .center) {
Text($count).textColor(.black)
Button("add").margin(.top,10).backgroundColor(.blue).onTap { [unowned self] in
self.step = self.step + 1
//修改内容后,要重新布局
self.updateFlexLayout()
}
}
Text("FlexPercent").backgroundColor(.orange).width(20%).height(20%)
1)cell继承ListCell,并设置isDynamicHeight值为true
class CellItem: ListCell {
override var isDynamicHeight: Bool { true }
@FlexboxViewBuilder func bodyView() -> [FlexboxView] {
return VStackView {
...
}
}
}
2)UITableView的rowHeight设置为UITableView.automaticDimension
UITableView().flex.expanded().apply {
$0.delegate = self
$0.dataSource = self
$0.register(CellItem.self, forCellReuseIdentifier: "cellID")
$0.rowHeight = UITableView.automaticDimension
}
1)cell继承GridCell,并设置isDynamicHeight值为true
private class FCollectionCell: GridCell {
override var isDynamicHeight: Bool { true }
@UState var text: String?
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .darkGray
}
override func bodyView() -> FlexboxView {
Text($text)
.fontSize(18)
.textColor(.orange)
.backgroundColor(.gray)
.numberOfLines(0)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2)UICollectionViewFlowLayout设置estimatdItemSize设置一个非0值开启自动计算高度
lazy var layout = UICollectionViewFlowLayout().then { layout in
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 10
//estimatdItemSize设置一个非0值开启自动计算高度,宽度要固定一个值,高度设置预估值
layout.estimatedItemSize = CGSize(width: UIScreen.main.bounds.width - 10*2, height: 100)
layout.itemSize = UICollectionViewFlowLayout.automaticSize
}
1)cell继承GridCell,并设置isDynamicHeight值为true,同时将scrollDirection设置为.horizontal
private class FCollectionCell: GridCell {
override var isDynamicHeight: Bool { true }
override var scrollDirection: UICollectionView.ScrollDirection { .horizontal }
@UState var text: String?
override init(frame: CGRect) {
super.init(frame: frame)
}
override func bodyView() -> FlexboxView {
Text($text).expanded().backgroundColor(.orange).cornerRadius(10).padding(.horizontal,20)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2)UICollectionViewFlowLayout设置estimatdItemSize设置一个非0值开启自动计算宽度
lazy var layout = UICollectionViewFlowLayout().then { layout in
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 10
//estimatdItemSize设置一个非0值开启自动计算宽度,高度要固定一个值,宽度设置预估值
layout.estimatedItemSize = CGSize(width: 10, height: 80)
layout.itemSize = UICollectionViewFlowLayout.automaticSize
}
UILabel()
.modifier
.text("链式语法")
.textColor(.orange)
.font(.systemFont(ofSize: 16))
等同于
let label = UILabel()
label.text = "test apply"
label.font = .systemFont(ofSize: 16)
label.textColor = .orange
只在UIView有效
UIView(frame: CGRect(x: 10, y: 100, width: 60, height: 60)).apply {
$0.backgroundColor = .blue
$0.layer.cornerRadius = 30
$0.clipsToBounds = true
}
UILabel().apply { label in
label.text = "test apply"
label.font = .systemFont(ofSize: 16)
label.textColor = .orange
}
等同于
let blueView = UIView(frame: CGRect(x: 10, y: 100, width: 60, height: 60))
blueView.backgroundColor = .blue
blueView.layer.cornerRadius = 30
blueView.clipsToBounds = true
let label = UILabel()
label.text = "test apply"
label.font = .systemFont(ofSize: 16)
label.textColor = .orange
-
主轴方向
-
布局方向 ltr,rtl
-
主轴方向子项分布 mainAxis
-
次轴方向子项分布 crossAxis
-
次轴方向多行子项分布
-
子项自身分布
-
flexbox文档
- justifyContent
- alignContent
- alignItems
- alignSelf
- flexDirection
- direction
- flexWrap
- position
- margin padding left right top bottom
- size width height minWidth
- flex 属性
- applyLayout
- markDirty
- sizeThatFits
- numberOfChildren
- isIncludedInLayout
- enabled
- display
- HStackView = Row
- VStackView = Column
- ZStackView = Stack 与Flutter和SwiftUI有差异,需要自己定义好size才有效果
- Wrap
- Text
- ImageView
- Space
- TextField
- TextView
- ScrollView
- VScrollView
- HScrollView
- ListCell = UITableViewCell
- GridCell = UICollectionViewCell
对于没有第二次封装的UIVIew,可以使用以下方法进行布局
UILabel().flex.makeLayout {
$0.margin(.left, 10).margin(.top, 100)
}.apply {
_ = $0.modifier
.text("flex.makeLayout写法")
.font(.systemFont(ofSize: 18))
.textColor(.orange)
}
var blowUp = false
let boxView = FlexContainer()
VStackView(mainAxis: .center, crossAxis: .center) {
boxView.flex.size(100).modifier.backgroundColor(.blue)
Button("动画").size(width: 100, height: 30)
.backgroundColor(.orange).margin(.top,10)
.onTap { [unowned self] in
UIView.animate(withDuration: 0.25, delay: 0) {
self.boxView.flex.size(self.blowUp ? 200 : 100)
self.updateFlexLayout()
self.blowUp = !self.blowUp
}
}
}
- 双向绑定
- UITableView封装 ListView ListItem
- UICollection封装 GridView H V
- 瀑布流
- 测试
- 支持SPM
- https://github.com/MihaelIsaev/UIKitPlus
- https://blog.eppz.eu/declarative-uikit-with-10-lines-of-code/
- https://github.com/hmlongco/RxSwiftWidgets
- https://tech.youzan.com/-sheng-ming-shi-uikitzai-you-zan-mei-ye-de-shi-jian/
- https://github.com/sakiyamaK/DeclarativeUIKit
- https://github.com/hmlongco/Builder/tree/main
- https://github.com/hainayanda/Draftsman
- https://github.com/nicklockwood/layout xml实现布局
- https://kazaimazai.com/swifty-uikit/
- https://github.com/KazaiMazai/SwiftyUIKit
- https://github.com/zhenglibao/FlexLib
- https://github.com/layoutBox/FlexLayout
- https://github.com/pujiaxin33/StackUI
FlexLayoutKit is under MIT license. See the LICENSE file for more info.