-
Notifications
You must be signed in to change notification settings - Fork 14
Getting started
Contents:
StateView
is simply a subclass of UIView
. You can add a StateView
to any UIView
as a subview or set a StateView
to be the view
of any UIViewController
.
You can create a new view controller with a root view set to your StateView
subclass by creating a subclass of StateViewController
. StateViewController
is a simple subclass of UIViewController
that adds the StateView
subclass you provide to its view
property.
To create a new view controller with your StateView
subclass as the root view, create a subclass of StateViewController
and provide your StateView
subclass in the viewDidLoad
method:
class HomeViewController: StateViewController {
override func viewDidLoad() {
super.viewDidLoad()
rootView = HomeView(parentViewController: self)
}
}
To add your StateView
subclass to an existing UIView
, simply add your subclass with addSubview()
and call the setRootView()
instance method on the new instance of your subclass:
let homeView = HomeView(parentViewController: self)
view.addSubview(homeView)
homeView.setRootView()
The call to setRootView()
will run the first pass of render()
and set the view up to update itself when your data changes. All children of this StateView
will update themselves without any further setup. Calling setRootView
is only required on the first StateView
subclass in a view hierarchy if that instance was added manually.
For more on StateView
and StateViewController
, visit the full documentation.
You can use place()
to add subviews to your StateView
. You can add any UIView
or StateView
as a subview.
On initial render and anytime your data changes, render()
is called. Any view placed with place()
in a run of render()
will be added as a subview. If the previous run of render()
also placed that view, the existing view will be preserved in the view hierarchy and passed new props
to update itself with. If a view was placed in the previous run of render()
and then not placed in the following run of render()
, the following run of render()
will cause that view to be removed from the view hierarchy.
Summary: Simply place()
a view to add it as a subview. Simply don't place()
that view in a following run of render()
to automatically remove that view. Decide which views to place and which not to place with conditional operators, functions, and anything else you can imagine in your render()
method.
You can call place()
with three parameters.
If you are placing another StateView
, place()
takes the type of the StateView
, the unique key you'd like to give that view as a String
, and AutoLayout constraints in the form of a SnapKit ConstraintMaker
.
let shippingSummary = place(ShippingSummary.self, "shipping summary") { make in
make.size.equalTo(self)
make.center.equalTo(self)
}
If you are placing a UIView
subclass that isn't a StateView
, place()
takes the initialized view as a UIView
, the unique key you'd like to give that view as a String
, and AutoLayout constraints in the form of a SnapKit ConstraintMaker
.
let button = UIButton(type: .System)
button.setTitle("Select image", forState: .Normal)
place(button, key: "button") { make in
make.size.equalTo(100)
make.center.equalTo(self)
}
Your StateView
subclass can initialize another StateView
subclass automatically, so you can simply pass the type of that StateView
to place()
. Your StateView
subclass can place an already initialized UIView
subclass that isn't a StateView
so you can configure that UIView
subclass before placing it. This is helpful for doing something like setting the text of a UILabel
. You can set the properties of your UILabel
inside the run of render()
and place()
that UILabel
once it's ready to go.
For more on place()
, visit the full documentation.
You can decide which views to place with place()
in render()
and which values to pass to placed views by looking at state
and props
.
You can keep values that describe the state of a view in state
:
self.state["selectedSong"] = Song(title: "This Must Be My Dream", artist: "The 1975")
self.state["displayingThankYou"] = true
self.state["selectedShipping"] = nil
You are encouraged to keep values in state
. The more descriptive you can be with keys and values in state
that work for you, the better. You are also encouraged to store objects of any types that work for you in state
, as state
is a dictionary with type [String: Any]
.
You can use state
as a dependable place you can look to see how your view looks onscreen without actually having to see it there. Anything in state
that is used to configure your StateView
or any of its children is always up-to-date in your view hierarchy. Simply update your state
and any view that depends on state
will update itself.
You can pass a value from state
or elsewhere to one of your subviews by passing the value as a prop
:
let nextButton = place(NextButton.self, "next button") { make in
make.center.equalTo(self)
make.height.equalTo(44)
make.width.equalTo(120)
}
nextButton.prop(forKey: SurveyViewKey.ButtonEnabled, is: true)
nextButton.prop(forKey: SurveyViewKey.ButtonTitle, is: "Continue")
let shippingSummary = place(ShippingSummary.self, "shipping summary") { make in
make.size.equalTo(self)
make.center.equalTo(self)
}
let selectedShipping = self.state["selectedShipping"]
shippingSummary.prop(forKey: CheckoutKey.Shipping, is: selectedShipping)
You can then access any of the values passed in props
in that subview by accessing self.props
. You are encouraged to pass objects of any types that work for you in props
, as props
allows values of type Any
.
You can use prop(forKey: StateKey, isLinkedToKeyInState: String) -> Void
as a shortcut to pass a value in state
as a prop
.
let shippingSummary = place(ShippingSummary.self, "shipping summary") { make in
make.width.equalTo(self)
make.height.equalTo(200)
make.centerX.equalTo(self)
make.bottom.equalTo(self)
}
shippingSummary.prop(forKey: CheckoutKey.Shipping, isLinkedToKeyInState: "selectedShipping")
This way you can easily connect one view's use of a value in state
with its subview's use of that same value.
You can communicate in the opposite direction, from subview to parent view, by passing the subview a function in props
. You are encouraged to pass objects of any types that work for you, as a subview can receive a function prop with type ([String: Any]->Void)
. You can pass a function that can be called to say that something was done, or one that can be given a selection from a group of items, or one that can do anything else you can imagine.
To pass values from a subview back to its parent view, pass a function as a prop
:
let canvas = place(CanvasView.self, key: "canvas") { make in
make.size.equalTo(self)
make.center.equalTo(self)
}
canvas.prop(forKey: CanvasKey.Image, isLinkedToKeyInState: "selectedImage")
canvas.prop(forKey: CanvasKey.DidPickImage) { values in
if let image = values["image"] as? UIImage {
self.state["selectedImage"] = image
}
}
You can then access and call that function from your subview by accessing self.props
.
You can easily accept a value in your StateView
coming back from a function prop
, set that value to be the value of a relevant key in state
, and watch your views update themselves at the arrival of the new value.
Summary: Simply update your state
, or a computed value outside of state
that you are passing as a prop
in your render()
method, and your view and its subviews will update themselves. render()
is a pure function of state
and props
. This means that given the same state
and props
, render()
will always produce the same result.
For more on state
and props
, visit the full documentation.
Contents:
Take a look at the known issues and help improve StateView.