Skip to content

duonghop/Akane

 
 

Repository files navigation

Akane

CocoaPods Carthage compatible Build Status

Akane is a iOS framework that helps you building better native apps by adopting an MVVM design pattern.

Main Goals
😅 Safety: minimize bad coding practices as much as possible
🔧 Feature-Oriented: adding and maintaining features is easy and, yes, safe
🔠 Component-Oriented: each visual feature you want to add to your app is a Component
✂️ fine-grained Separation of Concerns, which means:
👯 Much less merge conflicts
😎 A better understanding of your code

Each component, with Akane, is composed of:

  • ComponentViewController
  • ComponentViewModel
  • ComponentView

Why Akane, Or MVVM versus iOS MVC

iOS developers tend to write all their code into a unique and dedicated ViewController class. While this may have been OK some years ago, today's app codebases grow bigger and bigger. Maintaining a single, huge, ViewController file is a dangerous operation which often results in unpredictable side effects.

Akane makes you split your code into small components which are composed of multiple classes, some of which should sound familiar:

  • Model
  • View
  • View Model
  • ViewController

Model

The Model is the layer containing the classes that model your application business.

Songs, Movies, Books: all those classes or structs belong to this layer. They should contain no reference to any UIKit or Akane component.

struct User {
  enum Title: String {
    case sir
    case master
  }

  let username: String
  let title: Title
}

ViewModel

The ViewModel is where all your business logic should be implemented.

Please, Keep it agnostic: no reference to any View or ViewController should be present in your ViewModel. Also, Prefer ViewModel composition over inheritance: split your code into multiple ViewModel, each one dealing with one business case and then create another ViewModel to aggregate all those logics.

import Akane

class UserViewModel : ComponentViewModel {
  let user: Observable<User>?
  var disconnect: Command! = nil

  init(user: User) {
    self.user = Observable(user)
    self.disconnect = RelayCommand() { [unowned self] in
      self.user.next(nil)
    }
  }

  func isConnected() -> Bool {
    return self.user != nil
  }
}

View

Each View must correspond to one (and only one) ComponentViewModel. It should be a dedicated (business named) class, just like your ViewModel.

Please name the view meaningfully, by reflecting its business value: for instance BasketView, UserInfoView, etc. Also, a View is only about UI logic. Data must come from the ViewModel, by using binding to always be up-to-date.

The data flow between a ViewModel and its View is always unidirectional:

  • View <- ViewModel for data, through bindings
  • View -> ViewModel for actions, through commands: for instance, send a message or order a product.
import Akane

class UserView : UIView, ComponentView {
  @IBOutlet var labelUserHello: UILabel!
  @IBOutlet var buttonDisconnect: UIButton!

  func bindings(observer: ViewObserver, viewModel: UserViewModel) {
    // Bind 'user' with 'labelUserHello' 'text' using a converter
    observer.observe(viewModel.user)
            .convert(UserHelloConverter.self)
            .bind(to: self.labelUserHello.reactive.text)

    // bind 'disconnect' command with 'buttonDisconnect'
    observer.observe(viewModel.disconnect)
            .bind(to: self.buttonDisconnect)
  }
}

struct UserHelloConverter {
  typealias ValueType = User
  typealias ConvertValueType = String

  func convert(user: ValueType) -> ConvertValueType {
    let title = user.title.rawValue.uppercased()
    return "Good morning, \(title) \(user.username)"
  }
}

ViewController

ViewController, through ComponentController protocol, makes the link between ComponentViewModel and ComponentView.

Just pass your ComponentViewModel to your ViewController to bind it to its view.

application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {  

  let rootViewController = self.window.rootViewController as! ComponentViewController
  let user = User(username: "Bruce", title: .master)

  rootViewController.viewModel = UserViewModel(user: user)

  return true
}

You can even define your custom ViewControllers if you need to:

extension UserView {
  static func componentControllerClass() -> AnyComponentController.Type {
    return UserViewController.self
  }
}

class UserViewController : UIViewController, ComponentController {
  func viewDidLoad() {
    super.viewDidLoad()
    print("User component view loaded")
  }
}

Collections

Akane supports displaying collections of objects in UITableViews and UICollectionViews. Please read the Collections.md documentation to know more.

Installation

Akane supports installation via CocoaPods and Carthage.

CocoaPods

pod 'Akane'

Akane builds on top of Bond for managing bindings. If you do want to use your own library (like RxSwift), you can use Akane core only:

pod 'Akane/Core'

Carthage

Add github "akane/Akane" to your Cartfile. In order to use Akane Bindings and Akane Collections, you should also append github "ReactiveKit/Bond".

United We Stand

Akane works great by itself but is even better when combined with our other tools:

  • Gaikan, declarative view styling in Swift. Inspired by CSS modules.
  • Magellan, routing solution to decouple UI from navigation logic.

Contributing

This project was first developed by Xebia IT Architects and has been open-sourced since. We are committed to keeping on working and investing our time in Akane.

We encourage the community to contribute to the project by opening tickets and/or pull requests.

License

Akane is released under the MIT License. Please see the LICENSE file for details.

About

Lightweight native iOS MVVM framework

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Swift 97.3%
  • Ruby 1.6%
  • Objective-C 1.1%