1 // Copyright 2021-present 650 Industries. All rights reserved. 2 3 import React 4 5 /** 6 Custom component data extending `RCTComponentData`. Its main purpose is to handle event-based props (callbacks), 7 but it also simplifies capturing the view config so we can omit some reflections that React Native executes. 8 */ 9 @objc(EXComponentData) 10 public final class ComponentData: RCTComponentDataSwiftAdapter { 11 /** 12 Weak pointer to the holder of a module that the component data was created for. 13 */ 14 weak var moduleHolder: ModuleHolder? 15 16 /** 17 Initializer that additionally takes the original view module to have access to its definition. 18 */ 19 @objc 20 public init(viewModule: ViewModuleWrapper, managerClass: ViewModuleWrapper.Type, bridge: RCTBridge) { 21 self.moduleHolder = viewModule.wrappedModuleHolder 22 super.init(managerClass: managerClass, bridge: bridge, eventDispatcher: bridge.eventDispatcher()) 23 } 24 25 // MARK: RCTComponentData 26 27 /** 28 Creates a setter for the specific prop. For non-event props we just let React Native do its job. 29 Events are handled differently to conveniently use them in Swift. 30 */ 31 public override func createPropBlock(_ propName: String, isShadowView: Bool) -> RCTPropBlockAlias { 32 // Expo Modules Core doesn't support shadow views yet, so fall back to the default implementation. 33 if isShadowView { 34 return super.createPropBlock(propName, isShadowView: isShadowView) 35 } 36 37 // If the prop is defined as an event, create our own event setter. 38 if moduleHolder?.viewManager?.eventNames.contains(propName) == true { 39 return createEventSetter(eventName: propName, bridge: self.manager?.bridge) 40 } 41 42 // Otherwise also fall back to the default implementation. 43 return super.createPropBlock(propName, isShadowView: isShadowView) 44 } 45 46 public override func setProps(_ props: [String: Any], forView view: RCTComponent) { 47 guard let view = view as? UIView else { 48 log.warn("Given view is not an UIView") 49 return 50 } 51 guard let viewManager = moduleHolder?.viewManager else { 52 log.warn("View manager '\(self.name)' not found") 53 return 54 } 55 guard let appContext = moduleHolder?.appContext else { 56 log.warn("App context has been lost") 57 return 58 } 59 let propsDict = viewManager.propsDict() 60 var remainingProps = props 61 62 for (key, prop) in propsDict { 63 if props.index(forKey: key) == nil { 64 continue 65 } 66 67 let newValue = props[key] as Any 68 69 // TODO: @tsapeta: Figure out better way to rethrow errors from here. 70 try? prop.set(value: Conversions.fromNSObject(newValue), onView: view, appContext: appContext) 71 72 remainingProps.removeValue(forKey: key) 73 } 74 75 // Let the base class `RCTComponentData` handle all remaining props. 76 super.setProps(remainingProps, forView: view) 77 78 viewManager.callLifecycleMethods(withType: .didUpdateProps, forView: view) 79 } 80 81 /** 82 The base `RCTComponentData` class does some Objective-C dynamic calls in this function, but we don't 83 need to do these slow operations since the Sweet API gives us necessary details without reflections. 84 */ 85 public override func viewConfig() -> [String: Any] { 86 var propTypes: [String: Any] = [:] 87 var directEvents: [String] = [] 88 let superClass: AnyClass? = managerClass.superclass() 89 90 if let viewManager = moduleHolder?.viewManager { 91 for prop in viewManager.props { 92 // `id` allows every type to be passed in 93 propTypes[prop.name] = "id" 94 } 95 for eventName in viewManager.eventNames { 96 directEvents.append(RCTNormalizeInputEventName(eventName)) 97 propTypes[eventName] = "BOOL" 98 } 99 } 100 101 return [ 102 "propTypes": propTypes, 103 "directEvents": directEvents, 104 "bubblingEvents": [String](), 105 "baseModuleName": superClass?.moduleName() as Any 106 ] 107 } 108 } 109 110 /** 111 Creates a setter for the event prop. Used only by Paper. 112 */ 113 private func createEventSetter(eventName: String, bridge: RCTBridge?) -> RCTPropBlockAlias { 114 return { [weak bridge] (target: RCTComponent, value: Any) in 115 installEventDispatcher(forEvent: eventName, onView: target) { [weak target] (body: [String: Any]) in 116 if let target = target { 117 let componentEvent = RCTComponentEvent(name: eventName, viewTag: target.reactTag, body: body) 118 bridge?.eventDispatcher()?.send(componentEvent) 119 } 120 } 121 } 122 } 123