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 25 func updateProps() { 26 reactRootView?.appProperties = initialProps() 27 } 28 29 // MARK: UIViewController 30 31 override func viewDidLoad() { 32 super.viewDidLoad() 33 maybeRebuildRootView() 34 } 35 36 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 41 override func viewWillAppear(_ animated: Bool) { 42 super.viewWillAppear(animated) 43 forceRootViewToRenderHack() 44 reactRootView?.becomeFirstResponder() 45 } 46 47 override var shouldAutorotate: Bool { 48 get { 49 return true 50 } 51 } 52 53 override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 54 get { 55 return UIInterfaceOrientationMask.portrait 56 } 57 } 58 59 override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 60 get { 61 return UIInterfaceOrientation.portrait 62 } 63 } 64 65 @available(iOS 12.0, *) 66 override var overrideUserInterfaceStyle: UIUserInterfaceStyle { 67 get { 68 return manager.userInterfaceStyle 69 } 70 set {} 71 } 72 73 // MARK: private 74 75 private func initialProps() -> [String: Any] { 76 let isSimulator = TARGET_IPHONE_SIMULATOR > 0 77 78 return [ 79 "showOnboardingView": manager.shouldShowOnboarding(), 80 "appInfo": manager.getAppInfo(), 81 "devSettings": manager.getDevSettings(), 82 "menuPreferences": DevMenuPreferences.serialize(), 83 "uuid": UUID.init().uuidString, 84 "isDevice": !isSimulator, 85 ] 86 } 87 88 // RCTRootView assumes it is created on a loading bridge. 89 // in our case, the bridge has usually already loaded. so we need to prod the view. 90 private func forceRootViewToRenderHack() { 91 if !hasCalledJSLoadedNotification, let bridge = manager.appInstance.bridge { 92 let notification = Notification(name: DevMenuViewController.JavaScriptDidLoadNotification, object: nil, userInfo: ["bridge": bridge]) 93 94 reactRootView?.javaScriptDidLoad(notification) 95 hasCalledJSLoadedNotification = true 96 } 97 } 98 99 private func maybeRebuildRootView() { 100 guard let bridge = manager.appInstance.bridge else { 101 return 102 } 103 if reactRootView?.bridge != bridge { 104 if reactRootView != nil { 105 reactRootView?.removeFromSuperview() 106 reactRootView = nil 107 } 108 hasCalledJSLoadedNotification = false 109 reactRootView = DevMenuRootView(bridge: bridge, moduleName: "main", initialProperties: initialProps()) 110 reactRootView?.frame = view.bounds 111 reactRootView?.backgroundColor = UIColor.clear 112 113 if isViewLoaded, let reactRootView = reactRootView { 114 view.addSubview(reactRootView) 115 view.setNeedsLayout() 116 } 117 } else { 118 updateProps() 119 } 120 } 121 } 122