Here & Now

Here & Now is a weather app that I made to experiment with an iOS app architecture based on nestable components and RxSwift.

View controllers are deliberately minimal. They wire up their root ViewComponent and add its subview. Nested ViewComponents are used to break the UI code down into smaller logical pieces.

A ViewComponent is defined as:

protocol ViewComponent {
  // Streams used by the component
  associatedtype Inputs
    
  // Streams produced by the component
  associatedtype Outputs
    
  // Root view of the component, add as subview of parent component
  var view: UIView { get }
    
  init(disposedBy: DisposeBag)
    
  // Subscribe to input streams, export streams produced by component
  func start(_ inputs: Inputs) -> Outputs
}

extension ViewComponent {
  // Stop any services started by the component
  func stop() {}
}

Core UI logic is written in component protocol extensions for ease of testing. UI state changes are implemented as pure functions that operate on Rx types like Observable. For example, the map style is a function of the current time at the map location. A light style is used when it’s day time at the map location, and a dark style when it’s night time.

extension MapComponent {

  func mapStyle(forCameraPosition: Observable<GMSCameraPosition>,
                date: Observable<Date>) -> Observable<MapStyle> {
    let location = forCameraPosition.map(toLocation)
    return uiScheme(forLocation: location, date: date)
      .map { $0.style().mapStyle }
  }

  func uiScheme(forLocation: Observable<CLLocation>,
                date: Observable<Date>) -> Observable<UIScheme> {
    return Observable
      .combineLatest(forLocation, date) { (l, d) in
        if let dayTime = isDaytime(date: d, coordinate: l.coordinate) {
          return dayTime ? .light : .dark
        }
        return .light
      }
  }

  // ...
}

The source code for Here & Now is available on GitHub.

comments powered by Disqus