Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Application Development Techniques

Drivable UI

@dynamicMemberLookup
struct DriverFor<Base> {
  var base: Base
  
  init(base: Base) {
    self.base = base
  }
  
  subscript<T>(dynamicMember keyPath: KeyPath<Base, T>) -> T {
    return base[keyPath: keyPath]
  }

  subscript<T>(dynamicMember keyPath: WritableKeyPath<Base, T>) -> T {
    get { return base[keyPath: keyPath] }
    set { base[keyPath: keyPath] = newValue }
  }
}

protocol DriverConfigurator {
  associatedtype Drivable
  typealias Driver = DriverFor<Drivable>
  typealias DrivableKeyPathSet = PartialKeyPathSet<Drivable>
  
  ///
  func configure(_ driver: inout Driver, using set: DrivableKeyPathSet)
}

Updatable UI

// FIXME: This protocol would not allow sub-classes to extend the context.
protocol Updatable {
  associatedtype UpdateContext
  func update(using context: UpdateContext)
}

Partially configure / update UI

Drivable can be configured through the Driver. The user can decide how Drivable should be configured, either partly . DrivableKeyPathSet contains key-paths the called wishes to configure.

  • If the set is empty then it is recommend to do nothing.
  • If the set contains the identity key-path the user should usually fully configure the Drivable.
  • For any other key-path the user can conditionally configure the Drivable partially.
struct ConcreteConfigurator: DriverConfigurator {
  typealias Drivable = ConcreteDrivableType
  func configure(_ driver: inout Driver, using set: DrivableKeyPathSet) {
    if set.containsIdentity(or: \.first) {
      driver.base.first = /* we could obtain the value from some cache */
    }
    if set.containsIdentity(or: \.second.value) {
      // `.base` omitted due to `key-path member lookup`
      driver.second.value = /* we could compute the value */
    }
  }
}

Helper extensions / types / functions

extension Collection where Element: Equatable {
  /// Checks if current collection contains at least one
  /// element from `other` collection.
  func containsAny<C>(
    from other: C
  ) -> Bool where C: Collection, C.Element == Element {
    return other.contains(where: self.contains)
  }
}

/// A set of key-paths with the same `Root` type.
typealias PartialKeyPathSet<Root> = Set<PartialKeyPath<Root>>

// FIXME: Convert the extension to:
// `extension<Root> where Element: PartialKeyPath<Root>`
extension Collection where Element: AnyKeyPath {
  func containsIdentity<Root>(
    or keyPath: PartialKeyPath<Root>
  ) -> Bool where Element == PartialKeyPath<Root> {
    return containsAny(from: [\.self, keyPath])
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.