Skip to content
This repository has been archived by the owner on Jun 5, 2019. It is now read-only.

Getting started

Sahand Nayebaziz edited this page May 19, 2016 · 12 revisions

Contents:

Initializing a StateView

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 StateViewwill 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.

Adding subviews

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.

Using State and Props

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:

Clone this wiki locally