Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ All notable changes to this project will be documented in this file. Take a look
#### Navigator

* [#121](https://github.com/readium/swift-toolkit/issues/121) HTML `<audio>` and `<video>` elements are now paused when the resource moves off-screen in the EPUB navigator, rather than continuing to play in the background.
* Fixed the PDF navigator's content being inset by the surrounding safe area (e.g. the navigation bar) on iOS 27.

#### LCP

Expand Down
12 changes: 12 additions & 0 deletions Sources/Navigator/PDF/PDFDocumentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ public final class PDFDocumentView: PDFView {
documentViewDelegate?.pdfDocumentView(self, didGoTo: destination)
}

/// Since iOS 27, PDFKit lays out the pages inside the view's bounds inset
/// by its `safeAreaInsets`, instead of the full bounds. In paginated mode
/// this is not routed through the scroll view's `contentInset` at all, so
/// neither `contentInsetAdjustmentBehavior` nor zeroing `contentInset` has
/// any effect on it.
///
/// We report our own insets instead, so that PDFKit lays out the content
/// exactly where we want it.
override public var safeAreaInsets: UIEdgeInsets {
contentInset
}

override public func safeAreaInsetsDidChange() {
super.safeAreaInsetsDidChange()
updateContentInset()
Expand Down
68 changes: 11 additions & 57 deletions TestApp/Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,71 +4,25 @@
// available in the top-level LICENSE file of the project.
//

import Combine
import ReadiumShared
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?

private var app: AppModule!
private var subscriptions = Set<AnyCancellable>()
private(set) static var app: AppModule!

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
app = try! AppModule()

func makeItem(title: String, image: String) -> UITabBarItem {
UITabBarItem(
title: NSLocalizedString(title, comment: "Library tab title"),
image: UIImage(named: image),
tag: 0
)
}

// Library
let libraryViewController = app.library.rootViewController
libraryViewController.tabBarItem = makeItem(title: "bookshelf_tab", image: "bookshelf")

// OPDS Feeds
let opdsViewController = app.opds.rootViewController
opdsViewController.tabBarItem = makeItem(title: "catalogs_tab", image: "catalogs")

// About
let aboutViewController = app.aboutViewController
aboutViewController.tabBarItem = makeItem(title: "about_tab", image: "about")

let tabBarController = UITabBarController()
tabBarController.viewControllers = [
libraryViewController,
opdsViewController,
aboutViewController,
]

window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()

Self.app = try! AppModule()
return true
}

func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
guard let url = url.anyURL.absoluteURL, let vc = window?.rootViewController else {
return false
}

Task {
do {
try await app.library.importPublication(from: url, progress: { _ in })
} catch {
guard let error = error as? UserErrorConvertible else {
print(error)
return
}
vc.alert(error)
}
}

return true
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
UISceneConfiguration(
name: "Default Configuration",
sessionRole: connectingSceneSession.role
)
}
}
65 changes: 41 additions & 24 deletions TestApp/Sources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -216,30 +216,30 @@
<string>org.readium.lcpa</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>Zipped Audiobook</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>org.readium.zab</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>Audiobook</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>public.audio</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>Zipped Audiobook</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>org.readium.zab</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>Audiobook</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>public.audio</string>
</array>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
Expand All @@ -264,6 +264,23 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ struct UserPreferences<
isActive: isActive,
onClear: onClear
) {
HStack {
HStack(spacing: 4) {
Stepper(title,
onIncrement: onIncrement,
onDecrement: onDecrement)
Expand Down Expand Up @@ -835,7 +835,7 @@ struct UserPreferences<
onClear: @escaping () -> Void,
content: @escaping () -> V
) -> some View {
HStack {
HStack(spacing: 8) {
content()
.foregroundColor(isActive ? nil : .gray)

Expand Down
5 changes: 5 additions & 0 deletions TestApp/Sources/Reader/Common/ReaderViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,14 @@ class ReaderViewController<N: Navigator>: UIViewController,
override func viewDidLoad() {
super.viewDidLoad()

navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "xmark"), style: .plain, target: self, action: #selector(close))
navigationItem.rightBarButtonItems = makeNavigationBarButtons()
}

@objc private func close() {
dismiss(animated: true)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

Expand Down
8 changes: 3 additions & 5 deletions TestApp/Sources/Reader/ReaderModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,9 @@ final class ReaderModule: ReaderModuleAPI {
}

@MainActor func present(_ viewController: UIViewController) {
let backItem = UIBarButtonItem()
backItem.title = ""
viewController.navigationItem.backBarButtonItem = backItem
viewController.hidesBottomBarWhenPushed = true
navigationController.pushViewController(viewController, animated: true)
let host = UINavigationController(rootViewController: viewController)
Comment thread
mickael-menu marked this conversation as resolved.
host.modalPresentationStyle = .fullScreen
navigationController.present(host, animated: true)
}

guard let module = self.formatModules.first(where: { $0.supports(publication) }) else {
Expand Down
89 changes: 89 additions & 0 deletions TestApp/Sources/SceneDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// Copyright 2026 Readium Foundation. All rights reserved.
// Use of this source code is governed by the BSD-style license
// available in the top-level LICENSE file of the project.
//

import ReadiumShared
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?

private var app: AppModule {
AppDelegate.app
}

func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene else {
return
}

func makeItem(title: String, image: String) -> UITabBarItem {
UITabBarItem(
title: NSLocalizedString(title, comment: "Library tab title"),
image: UIImage(named: image),
tag: 0
)
}

// Library
let libraryViewController = app.library.rootViewController
libraryViewController.tabBarItem = makeItem(title: "bookshelf_tab", image: "bookshelf")

// OPDS Feeds
let opdsViewController = app.opds.rootViewController
opdsViewController.tabBarItem = makeItem(title: "catalogs_tab", image: "catalogs")

// About
let aboutViewController = app.aboutViewController
aboutViewController.tabBarItem = makeItem(title: "about_tab", image: "about")

let tabBarController = UITabBarController()
tabBarController.viewControllers = [
libraryViewController,
opdsViewController,
aboutViewController,
]

let window = UIWindow(windowScene: windowScene)
window.rootViewController = tabBarController
window.makeKeyAndVisible()
self.window = window

// The scene can be launched by opening a publication, in which case
// the URLs are delivered here instead of `scene(_:openURLContexts:)`.
importPublications(from: connectionOptions.urlContexts)
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
importPublications(from: URLContexts)
}

private func importPublications(from contexts: Set<UIOpenURLContext>) {
for context in contexts {
guard let url = context.url.anyURL.absoluteURL else {
continue
}

Task {
do {
try await app.library.importPublication(from: url, progress: { _ in })
} catch {
guard
let error = error as? UserErrorConvertible,
let vc = window?.rootViewController
else {
print(error)
return
}
vc.alert(error)
}
}
}
}
}
Loading