Giter Site home page Giter Site logo

m4tbat / katana-swift Goto Github PK

View Code? Open in Web Editor NEW

This project forked from bendingspoons/katana-swift

0.0 1.0 0.0 17.32 MB

Swift Apps in a Swoosh

Home Page: http://katana.bendingspoons.com

License: MIT License

Swift 98.43% Ruby 1.15% Objective-C 0.34% Shell 0.08%

katana-swift's Introduction

Katana

Twitter URL Build Status Docs Carthage compatible CocoaPods Licence

Katana is a modern Swift framework for writing iOS apps, strongly inspired by React and Redux, that gives structure to all the aspects of your app:

  • logic: the app state is entirely described by a single serializable data structure, and the only way to change the state is to dispatch an action. An action is an intent to transform the state, and contains all the information to do so. Because all the changes are centralized and are happening in a strict order, there are no subtle race conditions to watch out for.
  • UI: the UI is defined in terms of a tree of components declaratively described by props (the configuration data, i.e. a background color for a button) and state (the internal state data, i.e. the highlighted state for a button). This approach lets you think about components as isolated, reusable pieces of UI, since the way a component is rendered only depends on the current props and state of the component itself.
  • logic ↔️ UI: the UI components are connected to the app state and will be automatically updated on every state change. You control how they change, selecting the portion of app state that will feed the component props. To render this process as fast as possible, only the relevant portion of the UI is updated.
  • layout: Katana defines a concise language (inspired by Plastic) to describe fully responsive layouts that will gracefully scale at every aspect ratio or size, including font sizes and images.

We feel that Katana helped us a lot since we started using it in production. At Bending Spoons we use a lot of open source projects ourselves and we wanted to give something back to the community, hoping you will find this useful and possibly contribute. ❤️ 

Katana
🎙 Declaratively define your UI
📦 Store all your app state in a single place
💂 Clearly define what are the actions that can change the state
😎 Describe asynchronous actions like HTTP requests
💪 Support for middleware
🎩 Automatically update the UI when your app state changes
📐 Automatically scale your UI to every size and aspect ratio
🐎 Easily animate UI changes

Overview

Defining the logic of your app

Your entire app State is defined in a single struct, all the relevant application information should be placed here.

struct CounterState: State {
  var counter: Int = 0
}

The app State can only be modified by an Action. An Action represents an event that leads to a change in the State of the app. There are two kind of actions: SyncAction and AsyncAction. You define the behaviour of the action implementing the updatedState() method that will return the new app State based on the current app State and the Action itself.

struct IncrementCounter: SyncAction {
  var payload: ()

  func updatedState(currentState: State) -> State {
    guard var state = currentState as? CounterState else { fatalError("wrong state type") 	  }
    state.counter += 1
    return state
  }
}

The Store contains and manages your entire app State and it is responsible for dispatching Actions and updating the State.

let store = Store<CounterState>()
store.dispatch(IncrementCounter())

You can ask the Store to be notified about every change in the app State.

store.addListener() {
  // the app state has changed
}

Defining the UI

In Katana you declaratively describe a specific piece of UI providing a NodeDescription. Each NodeDescription will define the component in terms of:

  • StateType the internal state of the component (es. highlighted for a button)
  • PropsType the inputs coming from outside the component (es. backgroundColor for a view)
  • NativeView the UIKit element associated with the component
struct CounterScreen: NodeDescription {
	typealias StateType = EmptyState
	typealias PropsType = CounterScreenProps
	typealias NativeView = UIView
	
	var props: PropsType
}

Inside the props you want to specify all the inputs needed to render your NativeView and to feed your children components.

struct CounterScreenProps: NodeDescriptionProps {
  var count: Int = 0
  var frame: CGRect = .zero
  var alpha: CGFloat = 1.0
  var key: String?
}

When it's time to render the component, the method applyPropsToNativeView is called: this is where we need to adjust our nativeView to reflect the props and the state. Note that for common properties like frame, backgroundColor and more we already provide a standard applyPropsToNativeView so we got you covered.

struct CounterScreen: NodeDescription {
  ...
  public static func applyPropsToNativeView(props: PropsType,
  											state: StateType,
  											view: NativeView, ...) {
  	view.frame = props.frame
  	view.alpha = props.alpha
  }
}

NodeDescriptions lets you split the UI into small independent, reusable pieces. That's why it is very common for a NodeDescription to be composed by other NodeDescriptions as children, generating the UI tree. To define child components, implement the method childrenDescriptions.

struct CounterScreen: NodeDescription {
  ...
  public static func childrenDescriptions(props: PropsType,
  											state: StateType, ...) -> 	  [AnyNodeDescription] {
  	return [
  		Label(props: LabelProps.build({ (labelProps) in
          labelProps.key = CounterScreen.Keys.label.rawValue
          labelProps.textAlignment = .center
          labelProps.backgroundColor = .mediumAquamarine
          labelProps.text = NSAttributedString(string: "Count: \(props.count)")
      })),
      Button(props: ButtonProps.build({ (buttonProps) in
        buttonProps.key = CounterScreen.Keys.decrementButton.rawValue
        buttonProps.titles[.normal] = "Decrement"
        buttonProps.backgroundColor = .dogwoodRose
        buttonProps.titleColors = [.highlighted : .red]
        
        buttonProps.touchHandlers = [
          .touchUpInside : {
            dispatch(DecrementCounter())
          }
        ]
      })),
      Button(props: ButtonProps.build({ (buttonProps) in
        buttonProps.key = CounterScreen.Keys.incrementButton.rawValue
        buttonProps.titles[.normal] = "Increment"
        buttonProps.backgroundColor = .japaneseIndigo
        buttonProps.titleColors = [.highlighted : .red]
        
        buttonProps.touchHandlers = [
          .touchUpInside : {
            dispatch(IncrementCounter())
          }
        ]
      }))
  	]
  }
}

Attaching the UI to the Logic

The Renderer is responsible for rendering the UI tree and updating it when the Store changes.

You create a Renderer object starting from the top level NodeDescription and the Store.

renderer = Renderer(rootDescription: counterScreen, store: store)
renderer.render(in: view)

Every time a new app State is available, the Store dispatches an event that is captured by the Renderer and dispatched down to the tree of UI components. If you want a component to receive updates from the Store just declare its NodeDescription as ConnectedNodeDescription and implement the method connect to attach the app Store to the component props.

struct CounterScreen: ConnectedNodeDescription {
  ...
  static func connect(props: inout PropsType, to storeState: StateType) {
  	props.count = storeState.counter
  }
}

Layout of the UI

Katana has its own language (inspired by Plastic) to programmatically define fully responsive layouts that will gracefully scale at every aspect ratio or size, including font sizes and images. If you want to opt in, just implement the PlasticNodeDescription protocol and its layout method where you can define the layout of the children, based on the given referenceSize. The layout system will use the reference size to compute the proper scaling.

struct CounterScreen: ConnectedNodeDescription, PlasticNodeDescription, PlasticReferenceSizeable {
  ...
  static var referenceSize = CGSize(width: 640, height: 960)
  
  static func layout(views: ViewsContainer<CounterScreen.Keys>, props: PropsType, state: StateType) {
    let nativeView = views.nativeView
    
    let label = views[.label]!
    let decrementButton = views[.decrementButton]!
    let incrementButton = views[.incrementButton]!
    label.asHeader(nativeView)
    [label, decrementButton].fill(top: nativeView.top, bottom: nativeView.bottom)
    incrementButton.top = decrementButton.top
    incrementButton.bottom = decrementButton.bottom
    [decrementButton, incrementButton].fill(left: nativeView.left, right: nativeView.right)
  }
}

You can find the complete example here

Where to go from here

Getting started tutorial

We wrote a getting started tutorial. It is currently a work in progress, but it will be finished soon!

Give it a shot

pod try Katana

Explore sample projects

Animations Example Table Example Minesweeper Example

Check out the documentation

Documentation

Installation

Katana is available through CocoaPods and Carthage, you can also drop Katana.project into your Xcode project.

Requirements

  • iOS 8.4+

  • Xcode 8.0+

  • Swift 3.0+

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:

$ sudo gem install cocoapods

To integrate Katana into your Xcode project using CocoaPods, add it to your Podfile:

use_frameworks!
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.4'

pod 'Katana'
pod 'KatanaElements'

And run:

$ pod install

Carthage

Carthage is a decentralized dependency manager for Cocoa projects. You can install Carthage downloading and running the Carthage.pkg file you can download from here or you can install it using Homebrew simply by running:

$ brew update
$ brew install carthage

To integrate Katana into your Xcode project using Carthage, add it to your Cartfile:

github "Bendingspoons/katana-swift"

And Run:

$ carthage update

Then drag the built Katana.framework and KatanaElements.framework into your Xcode project.

Roadmap

  • immutable state

  • unidirectional data flow

  • sync/async/sideEffect actions

  • middlewares

  • automatic UI update

  • native redux-like implementation

  • native react-like implementation

  • declarative UI

  • leverage Plastic layout engine

  • support other layout engines

  • declarative Table element

  • macOS support

  • improve test coverage

  • expand documentation

  • write an example about wrapping UIKit view controllers

Get in touch

Special thanks

Contribute

  • If you've found a bug, open an issue;
  • If you have a feature request, open an issue;
  • If you want to contribute, submit a pull request;
  • If you have an idea on how to improve the framework or how to spread the word, please get in touch;
  • If you want to try the framework for your project or to write a demo, please send us the link of the repo.

We'll be happy to send you a sticker with the logo as a sign of appreciation for any meaningful contribution.

License

Katana is available under the MIT license.

About

Katana is maintained by Bending Spoons. We create our own tech products, used and loved by millions all around the world. Interested? Check us out!

katana-swift's People

Contributors

alaincaltieri avatar bolismauro avatar frankdilo avatar luca-papale avatar lucaquerella avatar m4tbat avatar paridebifulco avatar smaramba avatar sroddy avatar

Watchers

 avatar

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.