diff --git a/Source/Turbo/Visitable/Visitable.swift b/Source/Turbo/Visitable/Visitable.swift index db0c0403..557e175d 100644 --- a/Source/Turbo/Visitable/Visitable.swift +++ b/Source/Turbo/Visitable/Visitable.swift @@ -49,11 +49,22 @@ extension Visitable { func activateVisitableWebView(_ webView: WKWebView) { visitableView.activateWebView(webView, forVisitable: self) + // Explicitly designate the web view's scroll view as the tracked content + // scroll view so behaviour like UINavigationBar.prefersLargeTitles and the + // iOS 26 tab bar minimize trigger reliably, without depending on UIKit's + // subview-ordering heuristic. `.all` covers the top (nav bar) and bottom + // (tab bar) edges. + if #available(iOS 15.0, *) { + visitableViewController.setContentScrollView(webView.scrollView, for: .all) + } visitableDidActivateWebView(webView) } func deactivateVisitableWebView() { visitableWillDeactivateWebView() + if #available(iOS 15.0, *) { + visitableViewController.setContentScrollView(nil, for: .all) + } visitableView.deactivateWebView() visitableDidDeactivateWebView() } diff --git a/Source/Turbo/Visitable/VisitableView.swift b/Source/Turbo/Visitable/VisitableView.swift index f0adc362..bba9d761 100644 --- a/Source/Turbo/Visitable/VisitableView.swift +++ b/Source/Turbo/Visitable/VisitableView.swift @@ -24,7 +24,10 @@ open class VisitableView: UIView { open func activateWebView(_ webView: WKWebView, forVisitable visitable: Visitable) { self.webView = webView self.visitable = visitable - addSubview(webView) + // iOS 14 fallback: with no explicit contentScrollView, UIKit's heuristic requires + // the scrollable view to be the first subview for large-title / tab-bar-minimize + // to work. On iOS 15+ this is made robust by setContentScrollView in Visitable. + insertSubview(webView, at: 0) addFillConstraints(for: webView) installRefreshControl() showOrHideWebView() diff --git a/Tests/Turbo/VisitableViewControllerTests.swift b/Tests/Turbo/VisitableViewControllerTests.swift index a3bcb3ca..f5d4a170 100644 --- a/Tests/Turbo/VisitableViewControllerTests.swift +++ b/Tests/Turbo/VisitableViewControllerTests.swift @@ -47,6 +47,50 @@ class VisitableViewControllerTests: XCTestCase { XCTAssertEqual(viewController.initialVisitableURL, originalURL) XCTAssertEqual(viewController.currentVisitableURL, overriddenURL) } + + func test_webview_is_first_child_during_loading() { + XCTAssertEqual(viewController.visitableView.subviews.first, viewController.visitableView.activityIndicatorView) + + viewController.visitableView.activateWebView(webView, forVisitable: viewController) + + XCTAssertEqual(viewController.visitableView.subviews.first, webView) + XCTAssertEqual(viewController.visitableView.subviews.last, viewController.visitableView.activityIndicatorView) + } + + func test_webview_is_first_child_on_restore() { + XCTAssertEqual(viewController.visitableView.subviews.first, viewController.visitableView.activityIndicatorView) + + viewController.showVisitableScreenshot() + viewController.visitableView.activateWebView(webView, forVisitable: viewController) + + XCTAssertEqual(viewController.visitableView.subviews.count, 3) + XCTAssertEqual(viewController.visitableView.subviews.first, viewController.visitableView.webView) + + viewController.hideVisitableScreenshot() + + XCTAssertEqual(viewController.visitableView.subviews.count, 2) + XCTAssertEqual(viewController.visitableView.subviews.first, viewController.visitableView.webView) + } + + @available(iOS 15.0, *) + func test_activating_webview_designates_its_scroll_view_as_content_scroll_view() { + XCTAssertNil(viewController.contentScrollView(for: .top)) + + viewController.activateVisitableWebView(webView) + + XCTAssertEqual(viewController.contentScrollView(for: .top), webView.scrollView) + XCTAssertEqual(viewController.contentScrollView(for: .bottom), webView.scrollView) + } + + @available(iOS 15.0, *) + func test_deactivating_webview_clears_the_content_scroll_view() { + viewController.activateVisitableWebView(webView) + XCTAssertEqual(viewController.contentScrollView(for: .top), webView.scrollView) + + viewController.deactivateVisitableWebView() + + XCTAssertNil(viewController.contentScrollView(for: .top)) + } } final class WebViewSpy: WKWebView {