FlexLayoutKit
Flexbox in Swift,like SwiftUI and Flutter
Install / Use
/learn @BestYun/FlexLayoutKitREADME
FlexLayoutKit
基于facebook/yoga实现一个类似swiftui和Flutter的声明式UI框架
Requirements
- iOS 10.0+
- Xcode 12.5
- Swift 5.4
Installation
Cocoapods
pod 'FlexLayoutKit', '~> 0.5'
以下可选
pod 'FlexLayoutKit/SDWebImage'
pod 'FlexLayoutKit/Kingfisher' #需要ios 12以上
特性
- [x] FlexBox布局
- [x] 声明式语法,类似SwiftUI,如HStackView、VStackView、ZStackView,类似Flutter中的Row、Column、Stack、Wrap
- [x] 自动计算UITableViewCell 高度
- [x] 支持VScrollView、HScrollView,自动计算contentSize
- [x] 使用Wrap轻松实现流式布局,超过屏幕时会自动换行
- [x] Forin和if else DSL支持
- [x] 数据驱动UI,更新数据后自动会更新UI
- [x] 支持百分比
- [x] 链式语法
Usage 用法
Quick Start 快速开始
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()
}
}
example1
<img src="https://github.com/BestYun/FlexLayoutKit/blob/main/doc_imgs/example1.png" />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使用
HStackView {
ImageView().size(40).cornerRadius(10).backgroundColor(.gray.withAlphaComponent(0.2))
Spacer(10)
Text("Leo").textColor(.orange).fontSize(16,weight: .medium)
}
VStackView使用
VStackView(crossAxis: .center) {
ImageView().size(40).cornerRadius(10).backgroundColor(.gray.withAlphaComponent(0.2))
Spacer(10)
Text("Leo").textColor(.orange).fontSize(16,weight: .medium)
}
ZStackView使用
ZStackView {
FlexContainer(mainAxis: .center, crossAxis: .center){
Text("99")
}
.cornerRadius(15)
.backgroundColor(.red)
.top(0)
.right(0)
.size(30)
}
.size(100)
.backgroundColor(.orange)
Wrap用法
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)
}
}
}
ForIn用法
VScrollView {
for i in 0...100 {
FlexContainer(mainAxis: .center, crossAxis: .center) {
Text("\(i)")
}
.height(60)
.backgroundColor(.orange.withAlphaComponent(0.1))
.margin(.vertical,5)
}
}
if else用法
let state = true
HStackView {
if state {
Text("true")
}else{
Text("false")
}
}
@UState使用
@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%)
自动计算UITableViewCell 动态高度
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
}
自动计算UICollectionViewCell 动态高度
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
}
自动计算UICollectionViewCell 动态宽度
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
}
Modifier chain 链式语法
UILabel()
.modifier
.text("链式语法")
.textColor(.orange)
.font(.systemFont(ofSize: 16))
等同于
let label = UILabel()
label.text = "test apply"
label.font = .systemFont(ofSize: 16)
label.textColor = .orange
apply sugar
只在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
flexbox布局参考资料
FlexBox布局
<img src="https://github.com/BestYun/FlexLayoutKit/blob/main/doc_imgs/flex_terms.png" />-
主轴方向
-
布局方向 ltr,rtl
-
主轴方向子项分布 mainAxis
-
次轴方向子项分布 crossAxis
-
次轴方向多行子项分布
-
子项自身分布
-
flexbox文档
- justifyContent
- alignContent
- alignItems
- alignSelf
- flexDirection
- direction
- flexWrap
- position
API
- margin padding left right top bottom
- size width height minWidth
- flex 属性
- applyLayout
- markDirty
- sizeThatFits
- numberOfChildren
- isIncludedInLayout
- enabled
- display
UI
- HStackView = Row
- VStackView = Column
- ZStackView = Stack 与Flutter和SwiftUI有差异,需要自己定义好size才有效果
- Wrap
- Text
- ImageView
- Space
- TextField
- TextView
- ScrollView
- VScrollView
- HScrollView
- ListCell = UITableViewCell
- GridCell = UICollectionViewCell
Flex makeLayout
对
