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 return [ 77 "enableDevelopmentTools": true, 78 "showOnboardingView": manager.shouldShowOnboarding(), 79 "devMenuItems": manager.serializedDevMenuItems(), 80 "devMenuScreens": manager.serializedDevMenuScreens(), 81 "uuid": UUID.init().uuidString, 82 ] 83 } 84 85 // RCTRootView assumes it is created on a loading bridge. 86 // in our case, the bridge has usually already loaded. so we need to prod the view. 87 private func forceRootViewToRenderHack() { 88 if !hasCalledJSLoadedNotification, let bridge = manager.appInstance.bridge { 89 let notification = Notification(name: DevMenuViewController.JavaScriptDidLoadNotification, object: nil, userInfo: ["bridge": bridge]) 90 91 reactRootView?.javaScriptDidLoad(notification) 92 hasCalledJSLoadedNotification = true 93 } 94 } 95 96 private func maybeRebuildRootView() { 97 guard let bridge = manager.appInstance.bridge else { 98 return 99 } 100 if reactRootView?.bridge != bridge { 101 if reactRootView != nil { 102 reactRootView?.removeFromSuperview() 103 reactRootView = nil 104 } 105 hasCalledJSLoadedNotification = false 106 reactRootView = DevMenuRootView(bridge: bridge, moduleName: "main", initialProperties: initialProps()) 107 reactRootView?.frame = view.bounds 108 reactRootView?.backgroundColor = UIColor.clear 109 110 if isViewLoaded, let reactRootView = reactRootView { 111 view.addSubview(reactRootView) 112 view.setNeedsLayout() 113 } 114 } else { 115 updateProps() 116 } 117 } 118 } 119