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<EnvironmentType>: _TypeErasedComponentContext { var erasedComponent: AnyComponent<EnvironmentType> { get { preconditionFailure() } set(value) { preconditionFailure() } } var erasedState: ComponentState { preconditionFailure() } var erasedEnvironment: _Environment { get { return self.environment } set(value) { self.environment = value as! Environment<EnvironmentType> } } let layoutResult: ComponentLayoutResult var environment: Environment<EnvironmentType> init(environment: Environment<EnvironmentType>) { self.layoutResult = ComponentLayoutResult() self.environment = environment } } class ComponentContext<ComponentType: Component>: AnyComponentContext<ComponentType.EnvironmentType> { override var erasedComponent: AnyComponent<ComponentType.EnvironmentType> { 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<ComponentType.EnvironmentType>, state: ComponentType.State) { self.component = component self.state = state super.init(environment: environment) } } private var UIView_TypeErasedComponentContextKey: Int? extension UIView { func context<EnvironmentType>(component: AnyComponent<EnvironmentType>) -> AnyComponentContext<EnvironmentType> { return self.context(typeErasedComponent: component) as! AnyComponentContext<EnvironmentType> } func context<ComponentType: Component>(component: ComponentType) -> ComponentContext<ComponentType> { return self.context(typeErasedComponent: component) as! ComponentContext<ComponentType> } 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 { var _updated: ((Transition) -> Void)? var isUpdated: Bool = false public init() { } public final func updated(transition: Transition = .immediate) { self.isUpdated = true self._updated?(transition) } } public final class EmptyComponentState: ComponentState { } public protocol _TypeErasedComponent { func _makeView() -> UIView func _makeContext() -> _TypeErasedComponentContext func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> 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<EnvironmentType>, transition: Transition) -> CGSize } public extension Component { func _makeView() -> UIView { return self.makeView() } func _makeContext() -> _TypeErasedComponentContext { return ComponentContext<Self>(component: self, environment: Environment<EnvironmentType>(), state: self.makeState()) } func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> CGSize { let view = view as! Self.View return self.update(view: view, availableSize: availableSize, state: view.context(component: self).state, environment: environment as! Environment<EnvironmentType>, 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<EnvironmentType>: _TypeErasedComponent, Equatable { public let wrapped: _TypeErasedComponent public init<ComponentType: Component>(_ component: ComponentType) where ComponentType.EnvironmentType == EnvironmentType { self.wrapped = component } public static func ==(lhs: AnyComponent<EnvironmentType>, rhs: AnyComponent<EnvironmentType>) -> 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: Transition) -> CGSize { return self.wrapped._update(view: view, availableSize: availableSize, environment: environment as! Environment<EnvironmentType>, transition: transition) } public func _isEqual(to other: _TypeErasedComponent) -> Bool { return self.wrapped._isEqual(to: other) } } public final class AnyComponentWithIdentity<Environment>: Equatable { public let id: AnyHashable public let component: AnyComponent<Environment> public init<IdType: Hashable>(id: IdType, component: AnyComponent<Environment>) { self.id = AnyHashable(id) self.component = component } public static func == (lhs: AnyComponentWithIdentity<Environment>, rhs: AnyComponentWithIdentity<Environment>) -> Bool { if lhs.id != rhs.id { return false } if lhs.component != rhs.component { return false } return true } }