Introduce:
Youtube: https://youtu.be/3MOBCtIfRFA
Bilibili: https://www.bilibili.com/video/BV1Kh411J7vJ
Communicate:
Telegram: https://t.me/swift_puyopuyo
QQ: 830599565
A declaretive layout library for UIKit base on data driven written in 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 |
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 |
View depth
- | 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 |
A simple cell can be implemented like below. The subviews will be layout by specific rules. The buildin box follow the FlexBox rules.
/**
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)
LinearBox
, FlowBox
, ZBox
BoxView
|-- ZBox
|-- LinearBox
|-- HBox
|-- VBox
|-- FlowBox
|-- HFlow
|-- VFlow
The core of the layout is the description of View nodes. Measure
Regulator
Property | Description | Value |
---|---|---|
margin | Current view's margin | UIEdgeInset , default: .zero |
alignment | The alignment in superview | .none, .left, .top, .bottom, .vertCenter, .horzCenter , default: .none |
size | Size description | SizeDescription .fixed : fixed size.wrap : wrap contents.ratio fill up the residual space ratio, .aspectRatio : width / heightdefault: .wrap , Demo:Size Properties |
flowEnding | Only works in FlowBox , and the arranceCount = 0 , it means that current view is the last view in the row |
Bool , default: false |
activated | If will be calculate by parent box | Bool , default: true |
ZBox
extends Regulator
Property | Description | Value |
---|---|---|
justifyContent | Control all subview's alignment, if subview has set an alignment value, will be override by alignment | .left, .top, .bottom, .vertCenter, .horzCenter , default: .none |
padding | Current box's padding | UIEdgeInset , default: .zero |
LinearRegulator
extends Regulator
Property | Description | Value |
---|---|---|
space | Control the space between subviews | CGFloat , default: 0 |
format | Control the main axis alignment of subviews | .leading, .center, .between, .round, .trailing , default: .leading |
reverse | If reverse the order from adding | Bool default: false |
FlowRegulator
extends LinearRegulator
Property | Description | Value |
---|---|---|
arrange | Control the arrange count in each row, when arrange = 0 , will be separate by contents |
Int , default: 0 |
itemSpace | The space between items | CGFloat , default: 0 |
runSpace | The space between rows | CGFloat , default: 0 |
format | The format of the each row | .leading, .center, .between, .round, .trailing , default: .leading |
runFormat | The format of rows | .leading, .center, .between, .round, .trailing , default: .leading |
runRowSize | The row size in run direction | (Int) -> SizeDescription , default: .wrap(shrink: 1) |
Library provide a data driven api, to keep your UI always representing the right data. And relayout when something changes.
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: The dataDriven api is follow the RxSwift's logic, Be aware of the memory leaks
UIView
has an extension property, is an instance of protocol Animator {}
After BoxView layout, each subview's center
, bounds
will be assigned, The box will create animation for each view if needed.
PS: If subview has no animation, will use the nearest superview's animation, the superview must be instance of 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()
}
}
}
Demo: Animation
Because of the BoxView also a view, feel free to subclassing a Box, to reduce the view's hierarchy
class MyView: VBox {
override func buildBody() {
// do your view declare here
}
}
A complex view also need to be provide a data, and create some events, like click. In UIKit, also call DataSource
and Delegate
Library provide a code pattern to defind a data and emit events.
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
})
Demo: Style
// 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)
See Puyo+xxx.swift
You can extension more depende on your needs, expect your contribution
Jrwong, jr-wong@qq.com
Puyopuyo is available under the MIT license. See the LICENSE file for more info.