Looks like SwiftUI, define and layout the UIView using Swift DSL.
Under Construction! Get Away From Production!
add something in your Podfile
platform :ios, '9.0'
target 'LazyFishTest' do
use_frameworks!
pod 'LazyFish', :git => 'https://whereIsThePodGit', :branch => 'whichBranch'
endshow a Text Label in center:
self.view.arrangeViews {
UILabel()
.text("Hello World")
.alignment(.center)
}- you can keep all your old
UIViewControllers,UIViews and anything - writing UIView like SwiftUI
- support
iOS 9
- can not automatically refresh UI using if/else/for..in.. statements, using IfBlock(...)/ForEach(...) instead
- need to test
- lack of many UIView modifier (using KeyPath)
- lack of animation modifier
- lack of .. a lot of features
- 2022-10-09 Add UIView to SwiftUIPreview
@available(iOS 13.0, *)
struct Preview: PreviewProvider {
static var previews: some View {
SwiftUIViewPresent {
// uiviews
}
}
}- 2022-01-19 Add
GeometryReader.Binding<T>supportsdynamic member lookup
public struct GeometryProxy {
public var size: CGSize = .zero
}
GeometryReader { geo: Binding<GeometryProxy> in
UIButton("ABC")
.onAction {
print("button")
}
.frame(width: geo.size.width / 2, height: geo.size.height - 30 * 2)
.backgroundColor(.red)
.alignment([.top, .leading], value: 100)
// that "geo.size.width" is "dynamic member lookup"
}- 2022-01-13 Add a simple animation demo
@State private var alertScale: CGFloat = 0.1
view.property(\.transform, binding: $alertScale.map {
scale -> CGAffineTransform in
return .identity.scaledBy(x: scale, y: scale)
})
alertScale = 1- 2021-12-23
UIViewsupportskeyPathandbindingmodifier
Example: UILabel changing a Color
// static
UILabel()
.property(\.textColor, value: isTrue ? .green : .red)
// dynamic
@State var isTrue: Bool = true
UILabel()
.property(\.textColor, binding: $isTrue.map { b -> UIColor in
b ? .green : .red
})- 2021-12-16
Bindingsupportsjoin
Join 2 Binding objects, A + B = (A, B), or A + B = C
@State var text1: String = "cde"
@State var text2: String = "fgab"
@State var number3: Int = 100
let joined2Ojb: Binding<(String, Int)> = $text1.join($number3)
let joined3Obj: Binding<String> = $text1.join($text2) { s1, s2 in
return s1 + "_" + s2
}
label.text(binding: joined3Obj)- 2021-12-7
Bindingsupportsmap
turn Binding<A> into Binding<B>, create a Binding<Bool> where IfBlock needs
@State var text1: String = "abcdefg"
// Binding<String> -> Binding<Bool>
let mapCondition = $text.map { s in
return s.hasPrefix("abc")
}
IfBlock(mapCondition) {
// views...
}
// or
IfBlock($text.map { s in
return s.hasPrefix("abc")
}) {
// views...
}-
2021-12-6 IfElseView、ForEachView touches ignored
-
2021-11-18 add If, Else condition block
-
2021-10-28 add ForEach loop block
-
2021-10-9 First Commit
someView.arrangeViews {
view1
view2
view3
}which will create views like this
someView
\
---view1
---view2
---view3UIView {
// sub1...
// sub2...
// subn...
// align according to .alignment(...) function
}UIStackView(axis: .horizontal, distribution: .fill, alignment: .fill, spacing: 0) {
// sub1...
// sub2...
// subn...
}UIScrollView(.vertical, spacing: CGFloat = 0) {
// sub1...
// sub2...
// subn...
}scrollview
\
---internalStackView
\
---view1
---view2
---view3
Arguments: Binding<Bool>, returns: [IfBlockView], automatically shows or hides ifContent and elseContent
If you using if ... {} else ... {} condition pattern, it will not refresh. Just use this ugly IfBlock
IfBlock(self?.$showPage1) {
view1
view2
}
// or
IfBlock(self?.$showPage1) {
view1
view2
} contentElse: {
view3
}If the IfBlockView is in a Stack:
someStack
\
IfBlockView
\
---internalStackView
\
// if
---view1
---view2
// else
---view3 // isHiddenOtherwise:
someNotStack
\
IfBlockView
\
// if
---view1
---view2
// else
---view3 // isHiddenarguments: Binding<[T]>, return ForEachView<T>
as stated above, using for i in array {} will not refresh. Just use the ugly ForEach
ForEach($array) { item in
view1
view2
view3
}someStack
\
ForEachView<T>
\
---internalStackView
\
---view1
---view2
---view3someNotStack
\
ForEachView<T>
\
---view1
---view2
---view3TODO: Cell Reuse
Example:
@State var arr1: String = ["Dog", "Cat", "Fish"]
var arr2: String = ["Tom", "Jerry", "Butch"]
UITableView(style: .grouped) {
// dynamic section
TableViewSection(binding: $arr1) { item in
UILabel()
.text("dynamic row: \(item)")
.alignment(.leading, value: 20)
.alignment(.centerY)
} action: { [weak self] item in
// did selected cell
}
// staic section
TableViewSection(arr2) { item in
UILabel()
.text("static row: \(item)")
.alignment(.leading, value: 20)
.alignment(.centerY)
}
}if there is only one static section:
UITableView(style: .plain, array: testClasses) { item in
let title = item.name
UILabel().text(title)
.alignment(.leading, value: 20)
.alignment(.centerY)
} action: { [weak self] item in
// ...
}Using function to modify some properties, and return self, which looks like SwiftUI
Examples, defined in modifier.swift:
if you can't find any modifier that you wanted, use this keyPath function:
public extension UIView {
func property<Value>(_ keyPath: WritableKeyPath<Self, Value>, binding: Binding<Value>?) -> Self
func property<Value>(_ keyPath: WritableKeyPath<Self, Value>, value newValue: Value) -> Self
}public extension UIView {
func backgroundColor(_ color: UIColor) -> Self
func cornerRadius(_ cornerRadius: CGFloat) -> Self
func clipped() -> Self
func borderColor(_ color: UIColor) -> Self
func borderWidth(_ width: CGFloat) -> Self
func border(width: CGFloat, color: UIColor) -> Self
}public extension UILabel {
func text(_ text: String) -> Self
func textColor(_ color: UIColor) -> Self
func textAlignment(_ alignment: NSTextAlignment) -> Self
func numberOfLines(_ lines: Int) -> Self
func font(_ font: UIFont) -> Self
}
public extension UILabel {
// stateText
func text(binding stateText: Binding<String>) -> Self
}public extension UIControl {
typealias ActionBlock = () -> Void
func action(for event: Event = .touchUpInside, _ action: @escaping ActionBlock) -> Self
func textAlignment(_ alignment: ContentHorizontalAlignment) -> Self
}func alignment(_ edges: Alignment, value: CGFloat? = 0) -> Self
public struct Alignment: OptionSet {
public static let leading
public static let trailing
public static let top
public static let bottom
public static let allEdges: Alignment = [.leading, .trailing, .top, .bottom]
public static let centerX
public static let centerY
public static let center: Alignment = [centerX, centerY]
}func frame(width: CGFloat, height: CGFloat) -> Self
func frame(width: Binding<CGFloat>, height: Binding<CGFloat>) -> Selfit will create a PaddingView as superview
func padding(top: CGFloat? = nil, leading: CGFloat? = nil, bottom: CGFloat? = nil, trailing: CGFloat? = nil) -> Self@State property wrapper, makes properties observable:
@State var text: String = "abc"it will create these properties automatically:
var _text: State<String>
var $text: Binding<String> { get }UILabel, UIButton, UITextField with Binding<String>, will refresh its text while text changing.
Example: UILabel binds to a text
public extension UILabel {
func text(binding stateText: Binding<String>?) -> Self {
stateText?.addObserver(target: self) { [weak self] changed in
self?.text = changed.new
}
return self
}
}@State var text: String = "abc"
///...
self.view.arrangeViews {
UIlabel().text(self.$text)
}Binding<T> objects may not easy to use.
For example:
In this case, we need to turn Binding<String> into Binding<Bool> which IfBlock required, so we use map function.
@State var text: String = "abc"
IfBlock( $text.map { t in
return t.hasPrefix("a")
} ) {
// views
}Other example:
we want to combine two or more Binding objects into one, so we use join function.
@State var text: String = "abc"
@State var text2: String = "efg"
IfBlock( $text.join($text2) { t, t2 in
return t.hasPrefix(t2)
} ) {
// views
}GeometryReader in SwiftUI is very hard to understand, i'm trying to make it simple.
Our GeometryReader is a UIView, it passes a Binding<GeometryProxy> object in closure, you can using its size property to layout your views' size
GeometryReader { geo: Binding<GeometryProxy> in
UIButton("ABC")
.onAction {
print("button")
}
.frame(width: geo.size.width / 2, height: geo.size.height - 30 * 2)
.backgroundColor(.red)
.alignment([.top, .leading], value: 100)
}