CKSwift/View.swift (187 lines of code) (raw):

/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ import Foundation import ComponentKit // MARK: ComponentInflatable /// Can be inflated to a `Component` given a `SwiftComponentModel`. /// Anything that is `ComponentInflatable` can be added to a CKSwift view hierarchy. public protocol ComponentInflatable { func inflateComponent(with model: SwiftComponentModel?) -> Component } // MARK: View public protocol View : ComponentInflatable { associatedtype Body @ComponentBuilder var body: Body { get } } extension View where Self.Body == Never { public var body: Never { // Leaf views don't return fatalError("Attempting to call .body on a leaf view") } } public protocol ViewIdentifiable : TreeNodeLinkableView { associatedtype ID: Hashable var id: ID { get } } public typealias ReusableView = View & ViewIdentifiable & Equatable // MARK: Non-leaf component extension View where Self.Body == Component { public func inflateComponent(with model: SwiftComponentModel?) -> Component { // TODO: Reuse logic let hasScopeHandle = linkPropertyWrappersWithScopeHandle( forceRequireNode: model?.isEmpty == false) if hasScopeHandle == false { // If the current view doesn't require a scope handle and there is no view configuration // simply inflate the body to reduce the number of components generated. aka Stateless. return body.inflateComponent(with: model) } defer { if hasScopeHandle { CKSwiftPopClass() } } return SwiftComponent( self, body: body, model: model ) } } extension View where Self: ViewIdentifiable, Self.Body == Component { public func inflateComponent(with model: SwiftComponentModel?) -> Component { // TODO: Reuse logic linkPropertyWrappersWithScopeHandle() defer { CKSwiftPopClass() } return SwiftComponent( self, body: body, model: model ) } } extension View where Self: ViewIdentifiable & Equatable, Self.Body == Component { public func inflateComponent(with model: SwiftComponentModel?) -> Component { guard CKShouldCreateShellComponent() == false else { return SwiftReusableComponent( self, model: model ) } // TODO: Reuse logic linkPropertyWrappersWithScopeHandle() defer { CKSwiftPopClass() } return SwiftReusableComponent( self, body: body, model: model ) } } extension View where Self: ViewConfigurationRepresentable, Self.Body == Component { public func inflateComponent(with model: SwiftComponentModel?) -> Component { // TODO: Reuse logic let hasScopeHandle = linkPropertyWrappersWithScopeHandle( forceRequireNode: model?.isEmpty == false ) defer { if hasScopeHandle { CKSwiftPopClass() } } return SwiftComponent( self, viewConfiguration: viewConfiguration, model: model ) } } extension View where Self: ViewIdentifiable & ViewConfigurationRepresentable, Self.Body == Component { public func inflateComponent(with model: SwiftComponentModel?) -> Component { // TODO: Reuse logic linkPropertyWrappersWithScopeHandle() defer { CKSwiftPopClass() } return SwiftComponent( self, body: CKShouldCreateShellComponent() ? nil : body, viewConfiguration: viewConfiguration, model: model ) } } extension View where Self: ViewIdentifiable & ViewConfigurationRepresentable & Equatable, Self.Body == Component { public func inflateComponent(with model: SwiftComponentModel?) -> Component { // TODO: Reuse logic linkPropertyWrappersWithScopeHandle() defer { CKSwiftPopClass() } return SwiftReusableComponent( self, body: CKShouldCreateShellComponent() ? nil : body, viewConfiguration: viewConfiguration, model: model ) } } // MARK: Leaf component extension View where Self: ViewConfigurationRepresentable, Self.Body == Never { public func inflateComponent(with model: SwiftComponentModel?) -> Component { // TODO: Reuse logic let hasScopeHandle = linkPropertyWrappersWithScopeHandle( forceRequireNode: model?.isEmpty == false) defer { if hasScopeHandle { CKSwiftPopClass() } } return SwiftComponent( self, viewConfiguration: viewConfiguration, model: model ) } } extension View where Self: ViewConfigurationRepresentable & ViewIdentifiable, Self.Body == Never { public func inflateComponent(with model: SwiftComponentModel?) -> Component { // TODO: Reuse logic linkPropertyWrappersWithScopeHandle() defer { CKSwiftPopClass() } return SwiftComponent( self, viewConfiguration: viewConfiguration, model: model ) } } extension View where Self: ViewConfigurationRepresentable & ViewIdentifiable & Equatable, Self.Body == Never { public func inflateComponent(with model: SwiftComponentModel?) -> Component { // TODO: Reuse logic linkPropertyWrappersWithScopeHandle() defer { CKSwiftPopClass() } return SwiftReusableLeafComponent( self, viewConfiguration: viewConfiguration, model: model ) } } // MARK: Link private extension View { private var linkableItems: [TreeNodeLinkable] { Mirror(reflecting: self) .children .compactMap { $0.value as? TreeNodeLinkable } } private func link(linkableItems: [TreeNodeLinkable], id: Any?) { let node = CKSwiftCreateNode(SwiftComponent<Self>.self, id) linkableItems .enumerated() .forEach { index, item in item.link(with: node, at: index) } } func linkPropertyWrappersWithScopeHandle(forceRequireNode: Bool) -> Bool { let linkableItems = self.linkableItems guard linkableItems.isEmpty == false || forceRequireNode || self is TreeNodeLinkableView else { return false } link(linkableItems: linkableItems, id: nil) return true } } extension View where Self: ViewIdentifiable { func linkPropertyWrappersWithScopeHandle() { link(linkableItems: linkableItems, id: id) } }