diff --git a/CHANGELOG.md b/CHANGELOG.md index 093de27cb..151a1840f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ All notable changes to this project will be documented in this file. Take a look * The LCP License Document is now accessible via `publication.lcpLicense?.license`, even if the license validation fails with a status error or missing passphrase. This is useful for checking the end date of an expired license or renew a license. +### Fixed + +#### Navigator + +* The safe area insets strategy was adjusted to take into account changes in iOS/iPadOS 26. + ## [3.4.0] diff --git a/Sources/Navigator/EPUB/EPUBFixedSpreadView.swift b/Sources/Navigator/EPUB/EPUBFixedSpreadView.swift index 589d76a58..00cb45dcf 100644 --- a/Sources/Navigator/EPUB/EPUBFixedSpreadView.swift +++ b/Sources/Navigator/EPUB/EPUBFixedSpreadView.swift @@ -78,9 +78,10 @@ final class EPUBFixedSpreadView: EPUBSpreadView { return } - // Insets the bounds by the notch area (eg. iPhone X) to make sure that - // the content is not overlapped by the screen notch. - var insets = notchAreaInsets + // We use the window's safeAreaInsets instead of the view's because we + // only want to take into account the device notch and status bar, not + // the application's bars. + var insets = window?.safeAreaInsets ?? .zero // Use the same insets on the left and right side (the largest one) to // keep the pages centered on the screen even if the notches are not diff --git a/Sources/Navigator/EPUB/EPUBReflowableSpreadView.swift b/Sources/Navigator/EPUB/EPUBReflowableSpreadView.swift index ba7d5f728..3017be3d3 100644 --- a/Sources/Navigator/EPUB/EPUBReflowableSpreadView.swift +++ b/Sources/Navigator/EPUB/EPUBReflowableSpreadView.swift @@ -86,10 +86,15 @@ final class EPUBReflowableSpreadView: EPUBSpreadView { } private func updateContentInset() { + // We use the window's safeAreaInsets instead of the view's because we + // only want to take into account the device notch and status bar, not + // the application's bars. + let safeAreaInsets = window?.safeAreaInsets ?? .zero + if viewModel.scroll { topConstraint.constant = 0 bottomConstraint.constant = 0 - scrollView.contentInset = UIEdgeInsets(top: notchAreaInsets.top, left: 0, bottom: notchAreaInsets.bottom, right: 0) + scrollView.contentInset = UIEdgeInsets(top: safeAreaInsets.top, left: 0, bottom: safeAreaInsets.bottom, right: 0) } else { let contentInset = viewModel.config.contentInset @@ -98,9 +103,10 @@ final class EPUBReflowableSpreadView: EPUBSpreadView { ?? contentInset[.unspecified] ?? (top: 0, bottom: 0) - // Increases the insets by the notch area (eg. iPhone X) to make sure that the content is not overlapped by the screen notch. - insets.top += notchAreaInsets.top - insets.bottom += notchAreaInsets.bottom + // Increases the insets by the window's safe area insets area to + // make sure that the content is not overlapped by the screen notch. + insets.top += safeAreaInsets.top + insets.bottom += safeAreaInsets.bottom topConstraint.constant = insets.top bottomConstraint.constant = -insets.bottom diff --git a/Sources/Navigator/PDF/PDFDocumentView.swift b/Sources/Navigator/PDF/PDFDocumentView.swift index cd1489ea9..40c20e64e 100644 --- a/Sources/Navigator/PDF/PDFDocumentView.swift +++ b/Sources/Navigator/PDF/PDFDocumentView.swift @@ -40,9 +40,13 @@ public final class PDFDocumentView: PDFView { } private func updateContentInset() { - // Setting the horizontal values triggers shifts the content incorrectly, somehow. - firstScrollView?.contentInset.top = notchAreaInsets.top - firstScrollView?.contentInset.bottom = notchAreaInsets.bottom + // We use the window's safeAreaInsets instead of the view's because we + // only want to take into account the device notch and status bar, not + // the application's bars. + let insets = window?.safeAreaInsets ?? .zero + + firstScrollView?.contentInset.top = insets.top + firstScrollView?.contentInset.bottom = insets.bottom } override public func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { diff --git a/Sources/Navigator/Toolkit/Extensions/UIView.swift b/Sources/Navigator/Toolkit/Extensions/UIView.swift index 2ceade0c7..de65e35d0 100644 --- a/Sources/Navigator/Toolkit/Extensions/UIView.swift +++ b/Sources/Navigator/Toolkit/Extensions/UIView.swift @@ -8,43 +8,6 @@ import Foundation import UIKit extension UIView { - /// Returns the safe area insets taking only into account the device screen notches (eg. on - /// iPhone X), ignoring any UX safe area insets (eg. status bar, navigation bar). - /// - /// This can be used to layout the content in a way that makes sure it's not under the physical - /// notches, but at the same time is under the status and navigation bars (which is usually what - /// we want for a reader app). - /// - /// We use that instead of pinning the content directly to the safe area layout guides to avoid - /// the view shifting when the status bar is toggled. - var notchAreaInsets: UIEdgeInsets { - guard let window = window else { - return safeAreaInsets - } - - var windowSafeAreaInsets = window.safeAreaInsets - - // Trick to ignore the status bar on devices without notches (pre iPhone X). - // Notch height is usually at least 44pts tall. - let statusBarSize = window.windowScene?.statusBarManager?.statusBarFrame.size ?? .zero - // The frame is in the coordinate space of the window, so it might be swapped in landscape. - let statusBarHeight = min(statusBarSize.width, statusBarSize.height) - if statusBarHeight < 44, windowSafeAreaInsets.top == statusBarHeight { - windowSafeAreaInsets.top = 0 - } - - // We take the smallest value between the view's safeAreaInsets and the window's - // safeAreaInsets in case the view is not pinned to the screen edges. In which case, its - // safeAreaInsets will likely be empty and we don't want to take into account the screen - // notch. - return UIEdgeInsets( - top: min(windowSafeAreaInsets.top, safeAreaInsets.top), - left: min(windowSafeAreaInsets.left, safeAreaInsets.left), - bottom: min(windowSafeAreaInsets.bottom, safeAreaInsets.bottom), - right: min(windowSafeAreaInsets.right, safeAreaInsets.right) - ) - } - // Finds the first `UIScrollView` in the view hierarchy. // // https://medium.com/@wailord/the-particulars-of-the-safe-area-and-contentinsetadjustmentbehavior-in-ios-11-9b842018eeaa#077b