GithubHelp home page GithubHelp logo

sahandnayebaziz / stateview Goto Github PK

View Code? Open in Web Editor NEW
486.0 11.0 15.0 2.02 MB

UIViews that update themselves when your data changes, like React.

License: MIT License

Swift 93.66% Ruby 4.32% Objective-C 2.03%
swift react declarative uiview

stateview's Introduction

Views last updated: just now.

StateView is a UIView substitute that automatically updates itself when data changes.

Contents:

Overview

StateView is a UIView subclass that uses modern thinking and inspiration from what Facebook has done with React and the DOM to make displaying and updating your views easier, simpler, and more fun.

With StateView...

  • Your views update themselves when your data changes.
  • Your views add, remove, and update their subviews by themselves when your data changes.
  • Your views only update themselves if they need to. Each StateView calculates a diff (powered by a wonderful package named Dwifft) when your data changes to understand which subviews can stay, which can go, and which can be refreshed. Then, your StateView makes only those minimal changes.
  • You can write any custom view as a StateView.
  • You can write StateViews that contain other StateViews, standard UIViews, or a mix of both.
  • Your StateViews can automatically update a subview that is a standard UIView without any new code or special wrapping around that UIView.
  • You are encouraged to keep state across different views in your view hierarchy. Managing this state is easier when you use StateView and don't have to think about when, where, or how to call to methods like init, addSubview, and removeFromSuperview on your subviews.
  • You don't need to re-architect your app to be a declarative, functional, event-streamed, sequence-based, event-catching app to enjoy the benefits of reactivity and a family of views that are all pure functions of their state.

What's it like?

When you create your first StateView, you will become familiar with props, state, and render().

You can use props to pass values from one StateView to another, state to keep values in a StateView privately, and render() to describe how a StateView looks. StateView is simply a subclass of UIView that uses these three to update itself when your data changes.

Both props and state allow values of type Any to encourage you to keep and pass around anything that works for you.

When you add your first subview to a StateView inside render(), you will become familiar with place().

You can use place() to add a subview to your StateView. Then, your StateView will call addSubview and removeFromSuperview by itself so you don't have to.

In render(), simply look at your state and any passed-in props, and write code like...

override func render() {
	
	if let selectedImage = self.state["selectedImage"] as? UIImage {
		place(ImageViewWithTags.self, "image") { make in
			make.size.equalTo(self)
			make.center.equalTo(self)
		}
	} else {
		place(PlaceholderImageView.self, "placeholder") { make in
			make.size.equalTo(self)
			make.center.equalTo(self)
		}
	}
}

If in state, the key "selectedImage" contains a UIImage, an 'ImageViewWithTags' is placed, given the key "image", and given some AutoLayout constraints (using a wonderful library named SnapKit). If in state, "selectedImage" is nil or missing altogether, a PlaceholderImageView is placed instead.

Simply update your data and the view will update itself.

Change the value of "selectedImage" in state and this StateView will display, or not display, another custom StateView named ImageViewWithTags. Your StateView will call addSubview and removeFromSuperview by itself to render an updated view.

This, the way you can use render() and place(), is one of the most fun parts of using StateView.

Your code to decide which subviews to add to a StateView can go in render() where it will run any time your data changes. The results of a render() pass are then diffed with the results of the previous render() pass and your StateView only makes up the difference.

You can use props to pass values from one StateView to another. You can propagate new data in the state of one StateView to its children this way. In this case, to pass the new image in state to ImageViewWithTags, you can write code like...

override func render() {

	if let selectedImage = self.state["selectedImage"] as? UIImage {
		let imageView = place(ImageViewWithTags.self, "image") { make in
			make.size.equalTo(self)
			make.center.equalTo(self)
		}
		imageView.prop(forKey: Home.image, is: selectedImage)
	} else {
		place(PlaceholderImageView.self, "placeholder") { make in
			make.size.equalTo(self)
			make.center.equalTo(self)
		}
	}
}

Now this instance of ImageViewWithTags will receive selectedImage in its props. ImageViewWithTags can then access the new value of selectedImage anywhere in any of its methods, and especially in its own render method to do something like update a UIImageView.

ImageViewWithTags can access the value of selectedImage by using the same key used here, Home.image, which is an enum. You can create any number of your own enums to name your values any way that works for you. Any time state has a new value for selectedImage, ImageViewWithTags will receive the new value and update itself.

The second value in place, key, is used to help understand which views are the same between renders. The value of key can be anything you’d like, as long as no other subviews in that StateView have the same key. If the key of something you’ve placed changes between renders, StateView will render that subview from scratch since there won't be any existing views placed with the new key that can be preserved.

When you create your first StateView, you will become familiar with the following thought process:

  • How can this view change? I can leave variables in state to describe these different changes.
  • Now in render(), when state has this value for that key, I should place this subview, but if it has this value for that other key, I should place this one instead.
  • I can enumerate all the values I plan on passing between these views so that I can see them in one place and make it easy for others to understand data flow in my application.
  • That subview will get its props from this StateView's parent view, so I can pass along two of the props passed into this StateView into that StateView.
  • Which code is responsible for changing that value in state again? Oh, this callback here. I should add an initial value for that key in state to this StateView so that my render() has something concrete to use to decide what to display before that callback returns something.
  • Is there anything else I should put in render() since render runs any time my data changes? Is there anything else I'd like to be subtly different in my view when my data looks like this but not like that?

A full documentation of StateView and a getting started guide is in the wiki.

Sample apps

Frame is on the App Store and was made with StateView. With StateView, Frame ended up being just four classes, three of which are well under one hundred lines.

SwiftHub is an iOS app that displays Swift repositories from GitHub.

How does it work?

When you use a StateView, there is a ShadowView behind-the-scenes that helps understand which subviews can stay, which can go, and which can be refreshed between calls to render().

Each instance of StateView has a one-to-one matching instance of ShadowView that uses lightweight structs and references to keep a record of the view hierarchy and the data that the hierarchy represents.

Both render() and place() work closely with a StateView’s ShadowView to make the smallest number of changes to the view hierarchy to update what you see on screen.

When state changes in one of your views, a ShadowView orchestrates the calculation of the diff, the adding and removing of any needed or not needed subviews, and the passing of props from StateViews to their contained StateViews.

Installation

You can install StateView from CocoaPods. The latest release of StateView supports Swift 3.

pod 'StateView'

For Swift 2, use the last tag of StateView before StateView 2.

pod 'StateView', '~> 1.3'

What's next?

StateView was made to give iOS developers a safe, easy way to see their views update themselves. Above all, the goal is for StateView to be easy to use.

Any help towards making the methods, parameters, and patterns contained inside feel simpler despite the complexity of what StateView does is appreciated greatly. As are improvements to the performance of StateView.

If you’d like to contribute, take a look at the list of known issues in the wiki or start a new conversation in this project's issues!

Credits

StateView was written by Sahand Nayebaziz. StateView was inspired by React and the DOM.

License

StateView is released under the MIT license. See LICENSE for details.

stateview's People

Contributors

sahandnayebaziz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stateview's Issues

Using StateView and StateKeys

Hi, very excited about the promise of StateView but it seems like it's not quite working correctly for me. Here's what I have:

import StateView

struct STStashStateViewLabelKey: StateKey {
    var hashValue: Int = 1
}


class STStashChildView: StateView {

    override func render() {

        print("rendering STStashChildView")

        let label = UILabel()
        label.text = self.prop(withValueForKey: STStashStateViewLabelKey()) as! String
        place(label, key: "label") { (make) in
            make.center.equalTo(self)
        }
    }

    override func viewWillUpdate(newState: [String : Any?], newProps: [StateViewProp]) {
        print("viewWillUpdate in STStashChildView: \(newProps)")
    }

}

class STStashStateView: StateView {

    override func render() {

        print("rendering STStashStateView")

        let labelView = place(STStashChildView.self, key: "labelContainerView1") { (make) in
            make.center.equalTo(self)
        }
        labelView.prop(forKey: STStashStateViewLabelKey(), is: self.state["text"] as! String)

        let labelView2 = place(STStashChildView.self, key: "labelContainerView2") { (make) in
            make.center.equalTo(self)
        }
        labelView2.prop(forKey: STStashStateViewLabelKey(), is: "Same Text" as! String)
    }

    override func viewWillUpdate(newState: [String : Any?], newProps: [StateViewProp]) {
        print("viewWillUpdate in STStashStateView: \(newProps)")
    }

}

And I initiate state change like that somewhere else in my code:

let stashStateView = STStashStateView(parentViewController: self)
view.addSubview(stashStateView)
stashStateView.setRootView()

stashStateView.state = ["text": "some new text value"]

after stashStateView state changes I get viewWillUpdate and render calls in STStashStateView and in both STStashChildViews even though only one of them suppose to change due to new props but another one still renders "Same Text" unchanged. Seems like the diff doesn't work correctly or am I missing something?

Swift 3 Support

Once the next version of Xcode is out of beta, I will go through and make StateView compatible with Swift 3. Any help is of course appreciated!

Manual Installation

CocoaPods is an awesome tool and make our life really easier, but there are some devs who still don't know how to use them.

It would be cool to add the Manual installation guide in your README.md. You can take a look at my iOS Readme Template to see how you can do it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.