1 // Copyright 2015-present 650 Industries. All rights reserved. 2 3 import UIKit 4 5 class DevMenuViewController: UIViewController { 6 static let JavaScriptDidLoadNotification = Notification.Name("RCTJavaScriptDidLoadNotification") 7 static let ContentDidAppearNotification = Notification.Name("RCTContentDidAppearNotification") 8 9 private let manager: DevMenuManager 10 private var reactRootView: DevMenuRootView? 11 private var hasCalledJSLoadedNotification: Bool = false 12 13 init(manager: DevMenuManager) { 14 self.manager = manager 15 16 super.init(nibName: nil, bundle: nil) 17 edgesForExtendedLayout = UIRectEdge.init(rawValue: 0) 18 extendedLayoutIncludesOpaqueBars = true 19 } 20 21 required init?(coder: NSCoder) { 22 fatalError("init(coder:) has not been implemented") 23 } 24 updatePropsnull25 func updateProps() { 26 reactRootView?.appProperties = initialProps() 27 } 28 29 // MARK: UIViewController 30 viewDidLoadnull31 override func viewDidLoad() { 32 super.viewDidLoad() 33 maybeRebuildRootView() 34 } 35 viewWillLayoutSubviewsnull36 override func viewWillLayoutSubviews() { 37 super.viewWillLayoutSubviews() 38 reactRootView?.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height) 39 } 40 viewWillAppearnull41 override func viewWillAppear(_ animated: Bool) { 42 super.viewWillAppear(animated) 43 forceRootViewToRenderHack() 44 reactRootView?.becomeFirstResponder() 45 } 46 47 override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 48 get { 49 return UIInterfaceOrientationMask.all 50 } 51 } 52 53 @available(iOS 12.0, *) 54 override var overrideUserInterfaceStyle: UIUserInterfaceStyle { 55 get { 56 return manager.userInterfaceStyle 57 } 58 set {} 59 } 60 61 // MARK: private 62 initialPropsnull63 private func initialProps() -> [String: Any] { 64 let isSimulator = TARGET_IPHONE_SIMULATOR > 0 65 66 return [ 67 "showOnboardingView": manager.shouldShowOnboarding(), 68 "appInfo": manager.getAppInfo(), 69 "devSettings": manager.getDevSettings(), 70 "menuPreferences": DevMenuPreferences.serialize(), 71 "uuid": UUID.init().uuidString, 72 "isDevice": !isSimulator, 73 "registeredCallbacks": manager.registeredCallbacks.map { $0.name } 74 ] 75 } 76 77 // RCTRootView assumes it is created on a loading bridge. 78 // in our case, the bridge has usually already loaded. so we need to prod the view. forceRootViewToRenderHacknull79 private func forceRootViewToRenderHack() { 80 if !hasCalledJSLoadedNotification, let bridge = manager.appInstance.bridge { 81 let notification = Notification(name: DevMenuViewController.JavaScriptDidLoadNotification, object: nil, userInfo: ["bridge": bridge]) 82 83 reactRootView?.javaScriptDidLoad(notification) 84 hasCalledJSLoadedNotification = true 85 } 86 } 87 maybeRebuildRootViewnull88 private func maybeRebuildRootView() { 89 guard let bridge = manager.appInstance.bridge else { 90 return 91 } 92 if reactRootView?.bridge != bridge { 93 if reactRootView != nil { 94 reactRootView?.removeFromSuperview() 95 reactRootView = nil 96 } 97 hasCalledJSLoadedNotification = false 98 reactRootView = DevMenuRootView(bridge: bridge, moduleName: "main", initialProperties: initialProps()) 99 reactRootView?.frame = view.bounds 100 reactRootView?.backgroundColor = UIColor.clear 101 102 if isViewLoaded, let reactRootView = reactRootView { 103 view.addSubview(reactRootView) 104 view.setNeedsLayout() 105 } 106 } else { 107 updateProps() 108 } 109 } 110 } 111