BestYun / FlexLayoutKit

Flexbox in Swift,like SwiftUI and Flutter

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

FlexLayoutKit

Swift CocoaPods

基于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以上

特性

  • FlexBox布局
  • 声明式语法,类似SwiftUI,如HStackViewVStackViewZStackView,类似Flutter中的RowColumnStackWrap
  • 自动计算UITableViewCell 高度
  • 支持VScrollViewHScrollView,自动计算contentSize
  • 使用Wrap轻松实现流式布局,超过屏幕时会自动换行
  • Forinif else DSL支持
  • 数据驱动UI,更新数据后自动会更新UI
  • 支持百分比
  • 链式语法

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

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布局

  • 主轴方向

  • 布局方向 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

对于没有第二次封装的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
            }
        }
    
}

更新内容

Todo

参考

License

FlexLayoutKit is under MIT license. See the LICENSE file for more info.

About

Flexbox in Swift,like SwiftUI and Flutter

License:MIT License


Languages

Language:Swift 83.4%Language:Objective-C 15.5%Language:Ruby 1.1%