Puyopuyo 3.0.1

Puyopuyo 3.0.1

jrwong 维护。



Puyopuyo 3.0.1

  • Jrwong

Puyopuyo

CI Status Version Platform

中文

介绍

YouTube: https://youtu.be/3MOBCtIfRFA

哔哩哔哩: https://www.bilibili.com/video/BV1Kh411J7vJ

沟通

Telegram: https://t.me/swift_puyopuyo

QQ: 830599565

描述

一个基于数据驱动的 UIKit 布局库,用 Swift 编写。

要求

swift 5.1

安装

pod 'Puyopuyo'

布局开销

LinearLayout

- 5 10 30 50 80 100 120 150 180 200
Puyopuyo 0.133 0.2081 0.5259 0.7739 1.141 1.3921 1.6517 2.0799 2.4759 2.7499
Yoga 0.1549 0.272 0.678 1.0211 1.4729 1.8479 2.202 2.7499 3.2939 3.664
TangramKit 0.1859 0.3151 0.7939 1.1901 1.7089 2.1331 2.5599 3.201 3.8499 4.2488
UIStackView 0.5679 0.699 1.7571 2.7871 5.0063 6.901 9.392 13.741 19.3428 23.3399

LinearLayoutImage

FlowLayout

- 3 5 10 50 80 100 120 150 180 200
Puyopuyo 0.1299 0.1292 0.2298 0.9217 1.398 1.7228 2.0749 2.5968 3.1189 3.4549
Yoga 0.1039 0.1449 0.262 1.132 1.8072 2.2501 2.7101 3.39 4.0788 4.549
TangramKit 0.1339 0.123 0.209 0.9109 1.4457 1.7881 2.1469 2.675 3.2138 3.5629

FlowLayoutImage

视图深度

- 4 8 12 16 20 24 28 32 36 40
Puyopuyo 1.0719 1.5108 1.7547 1.6169 1.4448 1.739 2.0349 2.2962 2.671 2.8882
Yoga 2.3851 4.7698 6.6361 8.4819 11.2879 16.4551 23.0069 31.568 42.9677 55.4869
TangramKit 1.7189 2.711 2.932 3.117 3.7751 4.508 5.2759 6.1821 6.8421 7.6298

ViewDepthLayoutImage

使用方法

一个简单的单元格可以按照以下方式实现。子视图将按照特定规则布局。内置盒子遵循 FlexBox 规则。

/**
 VBox            HBox
 |-----------|   |-------------------|
 |Title      |   |Title   Description|
 |           |   |-------------------|
 |Description|
 |-----------|
 */
 
VBox().attach {
    UILabel().attach($0)
        .text("Title")
        .fontSize(20, weight: .bold)

    UILabel().attach($0)
        .text("Description")
}
.space(20)

// or
/**
VFlow       HFlow
|-------|   |-------|
|0  1  2|   |0  3  6|
|3  4  5|   |1  4  7|
|6  7   |   |2  5   |
|-------|   |-------|
 */
VFlow(count: 3).attach {
    for i in 0..<8 {
        UILabel().attach($0)
            .text(i.description)
    }
}
.space(20)

库提供了三种内置布局

LinearBoxFlowBoxZBox

BoxView
    |-- ZBox
    |-- LinearBox
        |-- HBox
        |-- VBox
    |-- FlowBox
        |-- HFlow
        |-- VFlow
    

布局的核心是视图节点的描述。MeasureRegulator

测量属性

属性 描述
margin 当前视图的边距 UIEdgeInset,默认:.zero
alignment 在父视图中的对齐方式 .none, .left, .top, .bottom, .vertCenter, .horzCenter,默认:.none
size 尺寸描述 尺寸描述
.fixed:固定大小
.wrap:包裹内容
.ratio填充剩余空间的比率,.aspectRatio:宽度/高度
默认:.wrap,示例:尺寸属性
flowEnding 仅在FlowBox中使用,当arranceCount = 0时,表示当前视图是行中的最后一个视图 Bool,默认:false
activated 将由父盒子计算 Bool,默认:true

调节器 & ZBox 属性

ZBox扩展了Regulator

属性 描述
justifyContent 控制所有子视图的对齐方式,如果子视图已设置对齐值,则将被对齐值覆盖 .left, .top, .bottom, .vertCenter, .horzCenter,默认:.none
padding 当前盒子的填充 UIEdgeInset,默认:.zero

线性调节器属性

LinearRegulator扩展了Regulator

属性 描述
空格 控制子视图之间的空间 CGFloat, 默认: 0
格式 控制子视图的主轴对齐方式 .leading, .center, .between, .round, .trailing, 默认: .leading
反转 如果反转添加顺序 Bool 默认: false

FlowRegulator 属性

FlowRegulator 扩展 LinearRegulator

属性 描述
排列 控制每行中的排列数量,当 arrange = 0 时,将根据内容分隔 Int, 默认: 0
项目间距 项目之间的空间 CGFloat, 默认: 0
行距 行之间的空间 CGFloat, 默认: 0
格式 每行的格式 .leading, .center, .between, .round, .trailing, 默认: .leading
行格式 行的格式 .leading, .center, .between, .round, .trailing, 默认: .leading
行尺寸 运行方向的行尺寸 (Int) -> SizeDescription, 默认: .wrap(shrink: 1)

数据驱动

库提供数据驱动 API,以保持你的 UI 始终代表正确的数据。并在有变化时重新布局。

let text = State("")
    
VBox().attach {
    UILabel().attach($0)
        .text("Title")

    UILabel().attach($0)
        .text(text)
}

// do when some data come back
text.value = "My Description"

// if you are using RxSwift that would be another good choise
// make `Observable` implements Outputing protocol
extension Observable: Outputing {
    public typealias OutputType = Element
    /// .... some code
}

func getDescription() -> Observable<String> {
    // get some description by network or other async works
}

// use in view declare
UILabel().attach($0)
    .text(getDescription())

PS:数据驱动 API 遵循 RxSwift 的逻辑,请注意内存泄漏。

动画

UIView 具有扩展属性,是 protocol Animator {} 的实例

BoxView 布局后,每个子视图的 centerbounds 将被分配,如果需要,方块将创建动画。

PS:如果子视图没有动画,将使用最近的父视图的动画,父视图必须是 BoxView 的实例

public struct ExpandAnimator: Animator {
    public init(duration: TimeInterval = 0.3) {
        self.duration = duration
    }

    public var duration: TimeInterval
    public func animate(_ view: UIView, size: CGSize, center: CGPoint, animations: @escaping () -> Void) {
        let realSize = view.bounds.size
        let realCenter = view.center
        if realSize != size || realCenter != center {
            if realSize == .zero, realCenter == .zero {
                runAsNoneAnimation {
                    view.center = center
                    let scale: CGFloat = 0.5
                    view.bounds.size = CGSize(width: size.width * scale, height: size.height * scale)
                    view.layer.transform = CATransform3DMakeRotation(.pi / 8 + .pi, 0, 0, 1)
                }
            }

            UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 2, initialSpringVelocity: 5, options: [.curveEaseOut, .overrideInheritedOptions, .overrideInheritedDuration], animations: {
                animations()
                if realSize == .zero, realCenter == .zero {
                    view.layer.transform = CATransform3DIdentity
                }
            }, completion: nil)
        } else {
            animations()
        }
    }
}

示例:动画

自定义视图

因为 BoxView 也是一个视图,你可以随意从 Box 继承,以减少视图的层次结构

class MyView: VBox {
    override func buildBody() {
        // do your view declare here
    }
}

状态和事件

复杂数据视图也需要提供数据,创建一些事件,例如点击事件。在UIKit中,也称为DataSourceDelegate

库提供了定义数据和发出事件的代码模式。

class MyView: VBox, Stateful, Eventable {
    // declare your dataSource you need
    struct ViewState {
        var name: String?
        var title: String?
    }
    // declare event that will occured
    enum Event {
        case onConfirmed
    }
    /// declare in Stateful implement by this class
    let state = State(ViewState())
    /// declare in Eventable implement by this class
    let emitter = SimpleIO<Event>()

    override func buildBody() {
        attach {
            UILabel().attach($0)
                .text(state.map(\.name)) // use map to transform value

            UILabel().attach($0)
                .text(binder.title) // use binder dynamic member lookup to find value

            UIButton().attach($0)
                .text("Confirm")
                .bind(event: .touchUpInside, input: emitter.asInput { _ in .onConfirmed })
        }
    }
}

// Use view

let state = State(MyView.ViewState())

MyView().attach($0)
    .state(state) // bind your data source
    .onEvent(Inputs { event in
        // do your logic when event occured
    })
    

样式

示例:样式

// declare style
let styles: [Style] = [
    (\UIView.backgroundColor).getStyle(with: .white),
    TapRippleStyle(),
    (\UILabel.text).getStyle(with: "Click"),
    (\UIView.isUserInteractionEnabled).getStyle(with: true)
]
// Use style
UILabel().attach()
    .styles(styles)

扩展

查看 Puyo+xxx.swift

您可以根据需要进一步扩展,期待您的贡献

作者

Jrwong, [email protected]

许可

Puyopuyo 可在 MIT 许可下使用。有关更多信息,请参阅 LICENSE 文件。