From 1de29ddda8466da49e3efe8e9e76bf7d11ede22f Mon Sep 17 00:00:00 2001 From: Patryk Klatka Date: Wed, 24 Apr 2024 14:39:12 +0200 Subject: [PATCH 1/3] Introduce `scrollEnabled` property, refactor a way of setting the `WebView` settings, and apply minor fixes to the native codebase (#190) --- packages/turbo/README.md | 4 + .../com/reactnativeturbowebview/RNSession.kt | 1 - .../RNVisitableView.kt | 81 ++++++++++++------- .../RNVisitableViewManager.kt | 4 + packages/turbo/ios/RNVisitableView.swift | 67 ++++++++++----- packages/turbo/ios/RNVisitableViewManager.m | 1 + packages/turbo/src/RNVisitableView.ts | 1 + packages/turbo/src/VisitableView.tsx | 3 + 8 files changed, 111 insertions(+), 51 deletions(-) diff --git a/packages/turbo/README.md b/packages/turbo/README.md index 4dbd7757..2a20bf1b 100644 --- a/packages/turbo/README.md +++ b/packages/turbo/README.md @@ -87,6 +87,10 @@ The name of the application as used in the user agent string. Please note that c Enables pull to refresh functionality. Default value is `true`. +### `scrollEnabled` + +Enables scrolling in the webview. Default value is `true`. + ### `stradaComponents` `VisitableView` supports defining [Strada components](https://strada.hotwired.dev/) that receive and reply to messages from web components that are present on the page within one session. This prop accepts an array of Strada components that will be registered in the webview. diff --git a/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNSession.kt b/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNSession.kt index 23d4ad59..6c289cc1 100644 --- a/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNSession.kt +++ b/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNSession.kt @@ -9,7 +9,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.whenStateAtLeast import com.facebook.react.BuildConfig -import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactApplicationContext import dev.hotwire.turbo.errors.TurboVisitError import dev.hotwire.turbo.session.TurboSession diff --git a/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNVisitableView.kt b/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNVisitableView.kt index 8c2bed42..c98dd298 100644 --- a/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNVisitableView.kt +++ b/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNVisitableView.kt @@ -2,8 +2,10 @@ package com.reactnativeturbowebview import android.content.Context import android.graphics.Bitmap +import android.view.MotionEvent import android.view.ViewGroup import android.webkit.CookieManager +import android.webkit.WebSettings import android.widget.LinearLayout import androidx.appcompat.widget.AppCompatImageView import androidx.core.view.isVisible @@ -16,12 +18,7 @@ import dev.hotwire.turbo.views.TurboView import dev.hotwire.turbo.views.TurboWebView import dev.hotwire.turbo.visit.TurboVisitOptions import dev.hotwire.turbo.R -import dev.hotwire.turbo.errors.HttpError -import dev.hotwire.turbo.errors.LoadError import dev.hotwire.turbo.errors.TurboVisitError -import dev.hotwire.turbo.errors.WebError -import dev.hotwire.turbo.errors.WebSslError -import dev.hotwire.turbo.visit.TurboVisitAction const val REFRESH_SCRIPT = "typeof Turbo.session.refresh === 'function'" + "? Turbo.session.refresh(document.baseURI)" + // Turbo 8+ @@ -46,6 +43,11 @@ class RNVisitableView(context: Context) : LinearLayout(context), SessionSubscrib lateinit var sessionHandle: String var applicationNameForUserAgent: String? = null + var scrollEnabled: Boolean = true + set(value) { + field = value + updateWebViewConfiguration() + } var pullToRefreshEnabled: Boolean = true set(value) { field = value @@ -53,14 +55,22 @@ class RNVisitableView(context: Context) : LinearLayout(context), SessionSubscrib } // Session - private val session: RNSession by lazy { - RNSessionManager.findOrCreateSession( - reactContext, - sessionHandle, - applicationNameForUserAgent - ) - } - private val webView: TurboWebView get() = session.webView + private var _session: RNSession? = null + private val session: RNSession? + get() { + if (_session != null) { + return _session + } + + if (!::sessionHandle.isInitialized) { + return null + } + + _session = RNSessionManager.findOrCreateSession(reactContext, sessionHandle, applicationNameForUserAgent) + return _session + } + + private val webView: TurboWebView? get() = session?.webView private var onConfirmHandler: ((result: Boolean) -> Unit)? = null private var onAlertHandler: (() -> Unit)? = null @@ -92,8 +102,22 @@ class RNVisitableView(context: Context) : LinearLayout(context), SessionSubscrib } } + private fun updateWebViewConfiguration() { + if (webView == null) return + + setOnTouchListener(webView!!, scrollEnabled) + } + + private fun setOnTouchListener(webView: TurboWebView, scrollEnabled: Boolean) { + if (!scrollEnabled) { + webView.setOnTouchListener(OnTouchListener { _, event -> event.action == MotionEvent.ACTION_MOVE }) + } else { + webView.setOnTouchListener(null) + } + } + private fun performVisit(restoreWithCachedSnapshot: Boolean, reload: Boolean) { - session.visit( + session?.visit( url = url, restoreWithCachedSnapshot = restoreWithCachedSnapshot, reload = reload, @@ -109,7 +133,7 @@ class RNVisitableView(context: Context) : LinearLayout(context), SessionSubscrib // Visit every time the WebView is reattached to the current Fragment. if (isWebViewAttachedToNewDestination) { val currentSessionVisitRestored = - !isInitialVisit && session.currentVisit?.destinationIdentifier == url.hashCode() && session.restoreCurrentVisit() + !isInitialVisit && session?.currentVisit?.destinationIdentifier == url.hashCode() && session?.restoreCurrentVisit() == true if (!currentSessionVisitRestored) { showProgressView() @@ -121,11 +145,11 @@ class RNVisitableView(context: Context) : LinearLayout(context), SessionSubscrib } override fun refresh() { - webView.evaluateJavascript(REFRESH_SCRIPT, null) + webView?.evaluateJavascript(REFRESH_SCRIPT, null) } override fun reload(displayProgress: Boolean) { - if (webView.url == null) return + if (webView?.url == null) return turboView.webViewRefresh?.apply { if (displayProgress && !isRefreshing) { @@ -164,7 +188,7 @@ class RNVisitableView(context: Context) : LinearLayout(context), SessionSubscrib override fun onAttachedToWindow() { super.onAttachedToWindow() - session.registerVisitableView(this) + session?.registerVisitableView(this) visit() } @@ -173,14 +197,14 @@ class RNVisitableView(context: Context) : LinearLayout(context), SessionSubscrib // This can happen when the user uses one session for different // bottom tabs. In this case, we need to remove the webview from // the parent before attaching it to the new one. - if (webView.parent != null) { - (webView.parent as ViewGroup).removeView(webView) + if (webView!!.parent != null) { + (webView!!.parent as ViewGroup).removeView(webView) } // Re-layout the TurboView before attaching to make page restorations work correctly. requestLayout() - turboView.attachWebView(webView) { attachedToNewDestination -> + turboView.attachWebView(webView!!) { attachedToNewDestination -> onReady(attachedToNewDestination) } } @@ -188,9 +212,9 @@ class RNVisitableView(context: Context) : LinearLayout(context), SessionSubscrib override fun detachWebView() { screenshotView() - (webView.parent as ViewGroup?)?.endViewTransition(webView) + (webView!!.parent as ViewGroup?)?.endViewTransition(webView) - turboView.detachWebView(webView) { + turboView.detachWebView(webView!!) { // Force layout to fix improper layout of the TurboWebView. forceLayout() } @@ -261,7 +285,7 @@ class RNVisitableView(context: Context) : LinearLayout(context), SessionSubscrib // region SessionSubscriber override fun injectJavaScript(script: String) { - webView.evaluateJavascript(script, null) + webView?.evaluateJavascript(script, null) } override fun handleMessage(message: WritableMap) { @@ -311,16 +335,17 @@ class RNVisitableView(context: Context) : LinearLayout(context), SessionSubscrib override fun visitRendered() { sendEvent(RNVisitableViewEvent.LOAD, Arguments.createMap().apply { - putString("title", webView.title) - putString("url", webView.url) + putString("title", webView!!.title) + putString("url", webView!!.url) }) + updateWebViewConfiguration() removeTransitionalViews() } override fun visitCompleted(completedOffline: Boolean) { sendEvent(RNVisitableViewEvent.LOAD, Arguments.createMap().apply { - putString("title", webView.title) - putString("url", webView.url) + putString("title", webView!!.title) + putString("url", webView!!.url) }) CookieManager .getInstance() diff --git a/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNVisitableViewManager.kt b/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNVisitableViewManager.kt index dfa84630..d2910f6d 100644 --- a/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNVisitableViewManager.kt +++ b/packages/turbo/android/src/main/java/com/reactnativeturbowebview/RNVisitableViewManager.kt @@ -57,6 +57,10 @@ class RNVisitableViewManager( view.pullToRefreshEnabled = pullToRefreshEnabled } + @ReactProp(name = "scrollEnabled") + fun setScrollEnabled(view: RNVisitableView, scrollEnabled: Boolean) { + view.scrollEnabled = scrollEnabled + } override fun getCommandsMap(): MutableMap = RNVisitableViewCommand.values() .associate { diff --git a/packages/turbo/ios/RNVisitableView.swift b/packages/turbo/ios/RNVisitableView.swift index b985c8fe..29c274f2 100644 --- a/packages/turbo/ios/RNVisitableView.swift +++ b/packages/turbo/ios/RNVisitableView.swift @@ -14,7 +14,6 @@ let REFRESH_SCRIPT = "typeof Turbo.session.refresh === 'function'" + class RNVisitableView: UIView, RNSessionSubscriber { var id: UUID = UUID() @objc var sessionHandle: NSString? = nil - @objc var applicationNameForUserAgent: NSString? = nil @objc var url: NSString = "" { didSet { if(url != oldValue) { @@ -22,11 +21,21 @@ class RNVisitableView: UIView, RNSessionSubscriber { } } } + @objc var applicationNameForUserAgent: NSString? = nil { + didSet { + webViewConfiguration.applicationNameForUserAgent = applicationNameForUserAgent as? String + } + } @objc var pullToRefreshEnabled: Bool = true { didSet { controller!.visitableView.allowsPullToRefresh = pullToRefreshEnabled } } + @objc var scrollEnabled: Bool = true { + didSet { + configureWebView() + } + } @objc var onMessage: RCTDirectEventBlock? @objc var onVisitProposal: RCTDirectEventBlock? @objc var onOpenExternalUrl: RCTDirectEventBlock? @@ -43,23 +52,35 @@ class RNVisitableView: UIView, RNSessionSubscriber { private var onConfirmHandler: ((Bool) -> Void)? private var onAlertHandler: (() -> Void)? - private lazy var session: RNSession = RNSessionManager.shared.findOrCreateSession(sessionHandle: sessionHandle!, webViewConfiguration: webViewConfiguration) - private lazy var webView: WKWebView = session.webView - private lazy var webViewConfiguration: WKWebViewConfiguration = { - let configuration = WKWebViewConfiguration() - configuration.applicationNameForUserAgent = applicationNameForUserAgent as String? - return configuration - }() + private var _session: RNSession? = nil + private var session: RNSession? { + if (_session != nil) { + return _session + } + + if (sessionHandle == nil) { + return nil + } + + _session = RNSessionManager.shared.findOrCreateSession(sessionHandle: sessionHandle!, webViewConfiguration: webViewConfiguration) + return _session + } + private var webView: WKWebView? { session?.webView } + private var webViewConfiguration: WKWebViewConfiguration = WKWebViewConfiguration() lazy var controller: RNVisitableViewController? = RNVisitableViewController(reactViewController: reactViewController(), delegate: self) private var isRefreshing: Bool { controller!.visitableView.isRefreshing } - - // var isModal: Bool { - // return controller.reactViewController()?.isModal() - // } + + private func configureWebView() { + if (webView == nil) { + return + } + + webView!.scrollView.isScrollEnabled = scrollEnabled + } override func willMove(toWindow newWindow: UIWindow?) { super.willMove(toWindow: newWindow) @@ -95,6 +116,7 @@ class RNVisitableView: UIView, RNSessionSubscriber { override func removeFromSuperview() { super.removeFromSuperview() + _session = nil controller = nil } @@ -105,7 +127,7 @@ class RNVisitableView: UIView, RNSessionSubscriber { } public func injectJavaScript(code: NSString) -> Void { - webView.evaluateJavaScript(code as String) + webView?.evaluateJavaScript(code as String) } public func sendAlertResult() -> Void { @@ -120,11 +142,11 @@ class RNVisitableView: UIView, RNSessionSubscriber { } public func reload() { - session.reload() + session?.reload() } public func refresh() { - webView.evaluateJavaScript(REFRESH_SCRIPT) + webView?.evaluateJavaScript(REFRESH_SCRIPT) } private func visit() { @@ -132,11 +154,11 @@ class RNVisitableView: UIView, RNSessionSubscriber { return } controller!.visitableURL = URL(string: String(url)) - session.visit(controller!) + session?.visit(controller!) } public func didProposeVisit(proposal: VisitProposal){ - if (webView.url == proposal.url && proposal.options.action == .replace) { + if (webView?.url == proposal.url && proposal.options.action == .replace) { // When reopening same URL we want to refresh webview refresh() } else { @@ -191,7 +213,7 @@ class RNVisitableView: UIView, RNSessionSubscriber { } func clearSessionSnapshotCache(){ - session.clearSnapshotCache() + session?.clearSnapshotCache() } func handleAlert(message: String, completionHandler: @escaping () -> Void) { @@ -214,11 +236,12 @@ class RNVisitableView: UIView, RNSessionSubscriber { extension RNVisitableView: RNVisitableViewControllerDelegate { func visitableWillAppear(visitable: Visitable) { - session.visitableViewWillAppear(view: self) + session?.visitableViewWillAppear(view: self) } func visitableDidAppear(visitable: Visitable) { - session.visitableViewDidAppear(view: self) + configureWebView() + session?.visitableViewDidAppear(view: self) } func visitableDidDisappear(visitable: Visitable) { @@ -227,8 +250,8 @@ extension RNVisitableView: RNVisitableViewControllerDelegate { func visitableDidRender(visitable: Visitable) { let event: [AnyHashable: Any] = [ - "title": webView.title!, - "url": webView.url! + "title": webView!.title!, + "url": webView!.url! ] onLoad?(event) } diff --git a/packages/turbo/ios/RNVisitableViewManager.m b/packages/turbo/ios/RNVisitableViewManager.m index 7254b3e9..c7f18cfe 100644 --- a/packages/turbo/ios/RNVisitableViewManager.m +++ b/packages/turbo/ios/RNVisitableViewManager.m @@ -17,6 +17,7 @@ @interface RCT_EXTERN_MODULE(RNVisitableViewManager, NSObject) RCT_EXPORT_VIEW_PROPERTY(sessionHandle, NSString) RCT_EXPORT_VIEW_PROPERTY(applicationNameForUserAgent, NSString) RCT_EXPORT_VIEW_PROPERTY(pullToRefreshEnabled, BOOL) + RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(onVisitProposal, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onOpenExternalUrl, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock) diff --git a/packages/turbo/src/RNVisitableView.ts b/packages/turbo/src/RNVisitableView.ts index 351b37a4..dd2144b3 100644 --- a/packages/turbo/src/RNVisitableView.ts +++ b/packages/turbo/src/RNVisitableView.ts @@ -27,6 +27,7 @@ export interface RNVisitableViewProps { sessionHandle?: string; applicationNameForUserAgent?: string; pullToRefreshEnabled: boolean; + scrollEnabled: boolean; onLoad?: (e: NativeSyntheticEvent) => void; onMessage?: (e: NativeSyntheticEvent) => void; onError?: (e: NativeSyntheticEvent) => void; diff --git a/packages/turbo/src/VisitableView.tsx b/packages/turbo/src/VisitableView.tsx index c44eb5ad..be82ceff 100644 --- a/packages/turbo/src/VisitableView.tsx +++ b/packages/turbo/src/VisitableView.tsx @@ -49,6 +49,7 @@ export interface Props { applicationNameForUserAgent?: string; stradaComponents?: StradaComponent[]; pullToRefreshEnabled?: boolean; + scrollEnabled?: boolean; renderLoading?: RenderLoading; renderError?: RenderError; onVisitProposal: (proposal: VisitProposal) => void; @@ -78,6 +79,7 @@ const VisitableView = React.forwardRef>( applicationNameForUserAgent, stradaComponents, pullToRefreshEnabled = true, + scrollEnabled = true, renderLoading, renderError, onLoad, @@ -214,6 +216,7 @@ const VisitableView = React.forwardRef>( sessionHandle={sessionHandle} applicationNameForUserAgent={resolvedApplicationNameForUserAgent} pullToRefreshEnabled={pullToRefreshEnabled} + scrollEnabled={scrollEnabled} onError={onErrorCombinedHandlers} onVisitProposal={handleVisitProposal} onMessage={handleOnMessage} From ef5d72d9fc04b2455d85aeec9427aaa773a6d934 Mon Sep 17 00:00:00 2001 From: Patryk Klatka Date: Wed, 24 Apr 2024 14:40:16 +0200 Subject: [PATCH 2/3] `turbo-ios`: remove `UIRefreshControl` layout constraints (#192) --- packages/turbo/patches/README.md | 13 ++++++++++++ ...tive-support.patch => turbo-android.patch} | 0 packages/turbo/patches/turbo-ios.patch | 21 +++++++++++++++++++ packages/turbo/scripts/build-turbo-android.sh | 2 +- packages/turbo/scripts/build-turbo-ios.sh | 6 +++++- 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 packages/turbo/patches/README.md rename packages/turbo/patches/{turbo-android-react-native-support.patch => turbo-android.patch} (100%) create mode 100644 packages/turbo/patches/turbo-ios.patch diff --git a/packages/turbo/patches/README.md b/packages/turbo/patches/README.md new file mode 100644 index 00000000..c201e18e --- /dev/null +++ b/packages/turbo/patches/README.md @@ -0,0 +1,13 @@ +# Patches for `turbo-ios` and `turbo-android` libraries + +This directory contains patches for the `turbo-ios` and `turbo-android` libraries. The patches are applied during the build process. Unfortunately, this process is necessary to make the libraries work correctly with `react-native-turbo`. + +## Changes + +### `turbo-ios` + +The patch removes the `NSLayoutConstraint` set in `VisitableView.installRefreshControl` method. This is necessary to make `contentInset` work properly with `UIRefreshControl`. + +### `turbo-android` + +The patch makes the necessary interfaces, classes and methods public so that they can be accessed from the `react-native-turbo` native land. diff --git a/packages/turbo/patches/turbo-android-react-native-support.patch b/packages/turbo/patches/turbo-android.patch similarity index 100% rename from packages/turbo/patches/turbo-android-react-native-support.patch rename to packages/turbo/patches/turbo-android.patch diff --git a/packages/turbo/patches/turbo-ios.patch b/packages/turbo/patches/turbo-ios.patch new file mode 100644 index 00000000..06fb01fa --- /dev/null +++ b/packages/turbo/patches/turbo-ios.patch @@ -0,0 +1,21 @@ +diff --git a/Source/Visitable/VisitableView.swift b/Source/Visitable/VisitableView.swift +index 12452b5..e9c37b8 100644 +--- a/Source/Visitable/VisitableView.swift ++++ b/Source/Visitable/VisitableView.swift +@@ -69,16 +69,6 @@ open class VisitableView: UIView { + + #if !targetEnvironment(macCatalyst) + scrollView.addSubview(refreshControl) +- +- /// Infer refresh control's default height from its frame, if given. +- /// Otherwise fallback to 60 (the default height). +- let refreshControlHeight = refreshControl.frame.height > 0 ? refreshControl.frame.height : 60 +- +- NSLayoutConstraint.activate([ +- refreshControl.centerXAnchor.constraint(equalTo: centerXAnchor), +- refreshControl.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), +- refreshControl.heightAnchor.constraint(equalToConstant: refreshControlHeight) +- ]) + #endif + } + diff --git a/packages/turbo/scripts/build-turbo-android.sh b/packages/turbo/scripts/build-turbo-android.sh index c1282fff..3c004289 100755 --- a/packages/turbo/scripts/build-turbo-android.sh +++ b/packages/turbo/scripts/build-turbo-android.sh @@ -1,7 +1,7 @@ TURBO_ANDROID_REPO_PATH="https://github.com/hotwired/turbo-android.git" TURBO_ANDROID_MAIN_SOURCE_DIR="./turbo/src/main/*" TURBO_ANDROID_VERSION=$1 -PATCH_FILE=$(realpath ./patches/turbo-android-react-native-support.patch) +PATCH_FILE=$(realpath ./patches/turbo-android.patch) TURBO_ANDROID_DIR=$(realpath ./android) DEPENDENCIES_GRADLE_FILE="turbo-android-dependencies.gradle" DEPENDENCY_REGEX="[a-zA-Z0-9.\-]+:[a-zA-Z0-9.\-]+:[0-9a-zA-Z.\-]+" diff --git a/packages/turbo/scripts/build-turbo-ios.sh b/packages/turbo/scripts/build-turbo-ios.sh index 6bb5f867..03cedd80 100644 --- a/packages/turbo/scripts/build-turbo-ios.sh +++ b/packages/turbo/scripts/build-turbo-ios.sh @@ -1,5 +1,6 @@ TURBO_IOS_REPO_PATH="https://github.com/hotwired/turbo-ios.git" TURBO_IOS_VERSION=$1 +PATCH_FILE=$(realpath ./patches/turbo-ios.patch) # First argument is the version tag if [ -z "$TURBO_IOS_VERSION" ] @@ -17,8 +18,11 @@ mkdir vendor cd vendor git clone --branch $TURBO_IOS_VERSION --depth 1 $TURBO_IOS_REPO_PATH -# Keep the Source folder and remove the rest +# Apply patch cd turbo-ios +git apply $PATCH_FILE + +# Keep the Source folder and remove the rest for file in *; do if [ "$file" != "Source" ]; then rm -rf $file From fe34a1a89f1ad5ca2a9b88b26bdc8fa043dd7b0d Mon Sep 17 00:00:00 2001 From: Patryk Klatka Date: Wed, 24 Apr 2024 14:41:24 +0200 Subject: [PATCH 3/3] iOS: ensure that all completion handlers have been called (#194) --- packages/turbo/ios/RNVisitableView.swift | 7 +++++++ packages/turbo/ios/RNVisitableViewController.swift | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/packages/turbo/ios/RNVisitableView.swift b/packages/turbo/ios/RNVisitableView.swift index 29c274f2..55e143cf 100644 --- a/packages/turbo/ios/RNVisitableView.swift +++ b/packages/turbo/ios/RNVisitableView.swift @@ -243,6 +243,13 @@ extension RNVisitableView: RNVisitableViewControllerDelegate { configureWebView() session?.visitableViewDidAppear(view: self) } + + func visitableWillDisappear(visitable: Visitable) { + // Ensure that all completion handlers have been called. + // Otherwise, an NSInternalInconsistencyException might occur. + sendAlertResult() + sendConfirmResult(result: "") + } func visitableDidDisappear(visitable: Visitable) { // No-op diff --git a/packages/turbo/ios/RNVisitableViewController.swift b/packages/turbo/ios/RNVisitableViewController.swift index 836dcf0e..bd5302af 100644 --- a/packages/turbo/ios/RNVisitableViewController.swift +++ b/packages/turbo/ios/RNVisitableViewController.swift @@ -14,6 +14,8 @@ public protocol RNVisitableViewControllerDelegate { func visitableDidAppear(visitable: Visitable) func visitableDidRender(visitable: Visitable) + + func visitableWillDisappear(visitable: Visitable) func visitableDidDisappear(visitable: Visitable) @@ -56,6 +58,11 @@ class RNVisitableViewController: UIViewController, Visitable { delegate?.visitableDidAppear(visitable: self) } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + delegate?.visitableWillDisappear(visitable: self) + } + override func viewDidDisappear(_ animated: Bool) { delegate?.visitableDidDisappear(visitable: self) }