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