import Foundation import UIKit import ObjectiveC public class ComponentLayoutResult { var availableSize: CGSize? var size: CGSize? } public protocol _TypeErasedComponentContext: AnyObject { var erasedEnvironment: _Environment { get } var erasedState: ComponentState { get } var layoutResult: ComponentLayoutResult { get } } class AnyComponentContext: _TypeErasedComponentContext { var erasedComponent: AnyComponent { get { preconditionFailure() } set(value) { preconditionFailure() } } var erasedState: ComponentState { preconditionFailure() } var erasedEnvironment: _Environment { get { return self.environment } set(value) { self.environment = value as! Environment } } let layoutResult: ComponentLayoutResult var environment: Environment init(environment: Environment) { self.layoutResult = ComponentLayoutResult() self.environment = environment } } class ComponentContext: AnyComponentContext { override var erasedComponent: AnyComponent { get { return AnyComponent(self.component) } set(value) { self.component = value.wrapped as! ComponentType } } var component: ComponentType let state: ComponentType.State override var erasedState: ComponentState { return self.state } init(component: ComponentType, environment: Environment, state: ComponentType.State) { self.component = component self.state = state super.init(environment: environment) } } private var UIView_TypeErasedComponentContextKey: Int? extension UIView { func context(component: AnyComponent) -> AnyComponentContext { return self.context(typeErasedComponent: component) as! AnyComponentContext } func context(component: ComponentType) -> ComponentContext { return self.context(typeErasedComponent: component) as! ComponentContext } func context(typeErasedComponent component: _TypeErasedComponent) -> _TypeErasedComponentContext{ if let context = objc_getAssociatedObject(self, &UIView_TypeErasedComponentContextKey) as? _TypeErasedComponentContext { return context } else { let context = component._makeContext() objc_setAssociatedObject(self, &UIView_TypeErasedComponentContextKey, context, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return context } } } open class ComponentState { open var _updated: ((ComponentTransition, Bool) -> Void)? var isUpdated: Bool = false public init() { } public final func updated(transition: ComponentTransition = .immediate, isLocal: Bool = false) { self.isUpdated = true self._updated?(transition, isLocal) } } public final class EmptyComponentState: ComponentState { } public protocol _TypeErasedComponent { func _makeView() -> UIView func _makeContext() -> _TypeErasedComponentContext func _update(view: UIView, availableSize: CGSize, environment: Any, transition: ComponentTransition) -> CGSize func _isEqual(to other: _TypeErasedComponent) -> Bool } public protocol ComponentTaggedView: UIView { func matches(tag: Any) -> Bool } public final class GenericComponentViewTag { public init() { } } public protocol Component: _TypeErasedComponent, Equatable { associatedtype EnvironmentType = Empty associatedtype View: UIView = UIView associatedtype State: ComponentState = EmptyComponentState func makeView() -> View func makeState() -> State func update(view: View, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize } public extension Component { func _makeView() -> UIView { return self.makeView() } func _makeContext() -> _TypeErasedComponentContext { return ComponentContext(component: self, environment: Environment(), state: self.makeState()) } func _update(view: UIView, availableSize: CGSize, environment: Any, transition: ComponentTransition) -> CGSize { let view = view as! Self.View return self.update(view: view, availableSize: availableSize, state: view.context(component: self).state, environment: environment as! Environment, transition: transition) } func _isEqual(to other: _TypeErasedComponent) -> Bool { if let other = other as? Self { return self == other } else { return false } } } public extension Component where Self.View == UIView { func makeView() -> UIView { return UIView() } } public extension Component where Self.State == EmptyComponentState { func makeState() -> State { return EmptyComponentState() } } public class ComponentGesture { public static func tap(action: @escaping() -> Void) -> ComponentGesture { preconditionFailure() } } public class AnyComponent: _TypeErasedComponent, Equatable { public let wrapped: _TypeErasedComponent public init(_ component: ComponentType) where ComponentType.EnvironmentType == EnvironmentType { self.wrapped = component } public static func ==(lhs: AnyComponent, rhs: AnyComponent) -> Bool { return lhs.wrapped._isEqual(to: rhs.wrapped) } public func _makeView() -> UIView { return self.wrapped._makeView() } public func _makeContext() -> _TypeErasedComponentContext { return self.wrapped._makeContext() } public func _update(view: UIView, availableSize: CGSize, environment: Any, transition: ComponentTransition) -> CGSize { return self.wrapped._update(view: view, availableSize: availableSize, environment: environment as! Environment, transition: transition) } public func _isEqual(to other: _TypeErasedComponent) -> Bool { return self.wrapped._isEqual(to: other) } } public final class AnyComponentWithIdentity: Equatable { public let id: AnyHashable public let component: AnyComponent public init(id: IdType, component: AnyComponent) { self.id = AnyHashable(id) self.component = component } public static func == (lhs: AnyComponentWithIdentity, rhs: AnyComponentWithIdentity) -> Bool { if lhs.id != rhs.id { return false } if lhs.component != rhs.component { return false } return true } }