diff --git a/Modules/Capabilities/DesignSystem/Sources/Elements/Navigation/NavigationBar.swift b/Modules/Capabilities/DesignSystem/Sources/Elements/Navigation/NavigationBar.swift index 57134150..dd5a43c9 100644 --- a/Modules/Capabilities/DesignSystem/Sources/Elements/Navigation/NavigationBar.swift +++ b/Modules/Capabilities/DesignSystem/Sources/Elements/Navigation/NavigationBar.swift @@ -15,7 +15,14 @@ public class NavigationBar: UINavigationBar { if #available(iOS 15.0, *) { compactScrollEdgeAppearance = NavigationBarAppearance() } - isTranslucent = false + + if #available(iOS 26.0, *) { + isOpaque = false + isTranslucent = true + } else { + isOpaque = true + isTranslucent = false + } } // MARK: Bar Button Appearance @@ -26,12 +33,12 @@ public class NavigationBar: UINavigationBar { public static let buttonTitleTextAttributes = [ NSAttributedString.Key.font: UIFont.navigationBarButtonFont, - .foregroundColor: UIColor.white, + .foregroundColor: UIColor.controlTint, ] public static let titleTextAttributes = [ NSAttributedString.Key.font: UIFont.navigationBarTitleFont, - .foregroundColor: UIColor.white, + .foregroundColor: UIColor.controlTint, ] // MARK: Boilerplate diff --git a/Modules/Capabilities/DesignSystem/Sources/Elements/Navigation/NavigationBarAppearance.swift b/Modules/Capabilities/DesignSystem/Sources/Elements/Navigation/NavigationBarAppearance.swift index 9dd940d7..56f399c1 100644 --- a/Modules/Capabilities/DesignSystem/Sources/Elements/Navigation/NavigationBarAppearance.swift +++ b/Modules/Capabilities/DesignSystem/Sources/Elements/Navigation/NavigationBarAppearance.swift @@ -6,8 +6,15 @@ import UIKit public class NavigationBarAppearance: UINavigationBarAppearance { public override init(idiom: UIUserInterfaceIdiom = UIDevice.current.userInterfaceIdiom) { super.init(idiom: idiom) - configureWithOpaqueBackground() - backgroundColor = .primaryDark + + if #available(iOS 26.0, *) { + configureWithTransparentBackground() + backgroundColor = .clear + } else { + configureWithOpaqueBackground() + backgroundColor = .primaryDark + } + largeTitleTextAttributes = NavigationBar.largeTitleTextAttributes titleTextAttributes = NavigationBar.titleTextAttributes backButtonAppearance.normal.titleTextAttributes = NavigationBar.buttonTitleTextAttributes diff --git a/Modules/Capabilities/EditingToolbar/Sources/ActionSet.swift b/Modules/Capabilities/EditingToolbar/Sources/ActionSet.swift index 33ff63f0..e2155cd4 100644 --- a/Modules/Capabilities/EditingToolbar/Sources/ActionSet.swift +++ b/Modules/Capabilities/EditingToolbar/Sources/ActionSet.swift @@ -27,7 +27,9 @@ public struct ActionSet { if sizeClass == .regular { UndoBarButtonItem(undoManager: undoManager, target: target) RedoBarButtonItem(undoManager: undoManager, target: target) + UIBarButtonItem.fixedSpace(0) ColorPickerBarButtonItem(target: target, color: currentColor) + UIBarButtonItem.fixedSpace(0) SeekBarButtonItem(target: target) if shouldShowQuickRedact { QuickRedactBarButtonItem(target: target) } diff --git a/Modules/Capabilities/EditingToolbar/Sources/DismissBarButtonItem.swift b/Modules/Capabilities/EditingToolbar/Sources/DismissBarButtonItem.swift index c6ce3f5d..8c40f919 100644 --- a/Modules/Capabilities/EditingToolbar/Sources/DismissBarButtonItem.swift +++ b/Modules/Capabilities/EditingToolbar/Sources/DismissBarButtonItem.swift @@ -13,6 +13,10 @@ public class DismissBarButtonItem: UIBarButtonItem { self.title = EditingToolbarStrings.DismissBarButtonItem.title self.target = self self.action = #selector(handleButton) + + if #available(iOS 26.0, *) { + self.tintColor = .clear + } } @objc private func handleButton() { diff --git a/Modules/Capabilities/PhotoLibrary/Sources/PhotoLibraryViewController.swift b/Modules/Capabilities/PhotoLibrary/Sources/PhotoLibraryViewController.swift index 49d8fc29..73bbb882 100644 --- a/Modules/Capabilities/PhotoLibrary/Sources/PhotoLibraryViewController.swift +++ b/Modules/Capabilities/PhotoLibrary/Sources/PhotoLibraryViewController.swift @@ -46,18 +46,24 @@ class PhotoLibraryViewController: UIViewController, UICollectionViewDelegate, UI override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - if shouldScrollToBottom { - libraryView.layoutIfNeeded() - libraryView.scrollToItem(at: dataSource.lastItemIndexPath, at: .bottom, animated: false) - shouldScrollToBottom = false - } - let cellCount = libraryView.numberOfItems(inSection: 0) if cellCount != dataSource.itemsCount { libraryView.reloadData() } } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + scrollToBottom() + } + + @objc func scrollToBottom() { + guard shouldScrollToBottom else { return } + libraryView.layoutIfNeeded() + libraryView.scrollToItem(at: dataSource.lastItemIndexPath, at: .bottom, animated: false) + shouldScrollToBottom = false + } + func reloadData() { libraryView.reloadData() } diff --git a/Modules/Legacy/Core/Sources/Application/AppViewController.swift b/Modules/Legacy/Core/Sources/Application/AppViewController.swift index cb709138..d5782e87 100644 --- a/Modules/Legacy/Core/Sources/Application/AppViewController.swift +++ b/Modules/Legacy/Core/Sources/Application/AppViewController.swift @@ -45,6 +45,14 @@ class AppViewController: UIViewController, PhotoEditorPresenting, DocumentScanni #endif } + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + switch traitCollection.userInterfaceIdiom { + case .phone, .tv, .carPlay, .mac, .unspecified: .portrait + case .pad, .vision: .all + @unknown default: .portrait + } + } + @objc func showPhotoLibrary() { transition(to: preferredViewController) } diff --git a/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingScrollView.swift b/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingScrollView.swift index 9a3b4833..37b560ad 100644 --- a/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingScrollView.swift +++ b/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingScrollView.swift @@ -1,19 +1,26 @@ // Created by Geoff Pado on 4/27/19. // Copyright © 2019 Cocoatype, LLC. All rights reserved. +import UIKit + import Geometry import Observations -import UIKit +import Redactions +import Tools class PhotoEditingScrollView: UIScrollView { init() { workspaceView = PhotoEditingWorkspaceView() - super.init(frame: .zero) + + if #available(iOS 26.0, *) { + contentInsetAdjustmentBehavior = .never + } + backgroundColor = .appBackground + delegate = scrollViewDelegate showsHorizontalScrollIndicator = false showsVerticalScrollIndicator = false - translatesAutoresizingMaskIntoConstraints = false addSubview(workspaceView) @@ -29,6 +36,11 @@ class PhotoEditingScrollView: UIScrollView { private(set) var workspaceView: PhotoEditingWorkspaceView + public var color: UIColor { + get { return workspaceView.color } + set(newColor) { workspaceView.color = newColor } + } + var image: UIImage? { get { return workspaceView.image } set(newImage) { @@ -41,6 +53,7 @@ class PhotoEditingScrollView: UIScrollView { get { return workspaceView.textObservations } set(newTextObservations) { workspaceView.textObservations = newTextObservations + updateAccessibilityElements() } } @@ -55,6 +68,60 @@ class PhotoEditingScrollView: UIScrollView { workspaceView.redactableCharacterObservations } + var seekPreviewObservations: [CharacterObservation] { + get { workspaceView.seekPreviewObservations } + set(newTextObservations) { + workspaceView.seekPreviewObservations = newTextObservations + } + } + + public var highlighterTool: HighlighterTool { + get { return workspaceView.highlighterTool } + set(newTool) { + workspaceView.highlighterTool = newTool + } + } + + // MARK: Redaction + + public var redactions: [Redaction] { + workspaceView.redactions + } + + public func add(_ redactions: [Redaction]) { + workspaceView.add(redactions) + } + + func redact(_ observations: [any TextObservation], joinSiblings: Bool) { + workspaceView.redact(observations, joinSiblings: joinSiblings) + updateAccessibilityElements() + } + + func unredact(_ observation: any TextObservation) { + workspaceView.unredact(observation) + updateAccessibilityElements() + } + + // MARK: Accessibility + + private func updateAccessibilityElements() { + let wordObservations = recognizedTextObservations?.flatMap(\.allWordObservations) ?? [] + let accessibilityElements = wordObservations.map { observation in + WordObservationAccessibilityElement(observation, in: workspaceView) { [weak self] observation, isRedacted -> Bool in + if isRedacted { + self?.unredact(observation) + } else { + self?.redact([observation], joinSiblings: true) + } + + return true + } + } + + workspaceView.accessibilityElements = accessibilityElements + workspaceView.accessibilityCustomRotors = [RedactedWordObservationRotor(accessibilityElements: accessibilityElements)] + } + // MARK: View Lifecycle override func layoutSubviews() { @@ -103,6 +170,8 @@ class PhotoEditingScrollView: UIScrollView { // MARK: Boilerplate + private let scrollViewDelegate = PhotoEditingScrollViewDelegate() + @available(*, unavailable) required init(coder: NSCoder) { let className = String(describing: type(of: self)) diff --git a/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingScrollViewDelegate.swift b/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingScrollViewDelegate.swift new file mode 100644 index 00000000..7b476404 --- /dev/null +++ b/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingScrollViewDelegate.swift @@ -0,0 +1,22 @@ +// Created by Geoff Pado on 8/31/25. +// Copyright © 2025 Cocoatype, LLC. All rights reserved. + +import UIKit + +class PhotoEditingScrollViewDelegate: NSObject, UIScrollViewDelegate { + func viewForZooming(in scrollView: UIScrollView) -> UIView? { + guard let scrollView = scrollView as? PhotoEditingScrollView else { + return nil + } + + return scrollView.workspaceView + } + + func scrollViewDidZoom(_ scrollView: UIScrollView) { + guard let scrollView = scrollView as? PhotoEditingScrollView else { + return + } + + scrollView.workspaceView.scrollViewDidZoom(to: scrollView.zoomScale) + } +} diff --git a/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingView.swift b/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingView.swift deleted file mode 100644 index 496c8517..00000000 --- a/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingView.swift +++ /dev/null @@ -1,125 +0,0 @@ -// Created by Geoff Pado on 5/27/19. -// Copyright © 2019 Cocoatype, LLC. All rights reserved. - -import Observations -import Redactions -import Tools -import UIKit - -public class PhotoEditingView: UIView, UIScrollViewDelegate { - public init() { - super.init(frame: .zero) - backgroundColor = .appBackground - - photoScrollView.delegate = self - addSubview(photoScrollView) - - NSLayoutConstraint.activate([ - photoScrollView.widthAnchor.constraint(equalTo: safeAreaLayoutGuide.widthAnchor), - photoScrollView.heightAnchor.constraint(equalTo: safeAreaLayoutGuide.heightAnchor), - photoScrollView.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor), - photoScrollView.centerYAnchor.constraint(equalTo: safeAreaLayoutGuide.centerYAnchor), - ]) - } - - public var color: UIColor { - get { return workspaceView.color } - set(newColor) { workspaceView.color = newColor } - } - - public var image: UIImage? { - get { return photoScrollView.image } - set(newImage) { photoScrollView.image = newImage } - } - - public var textObservations: [TextRectangleObservation]? { - didSet { - photoScrollView.textObservations = textObservations - } - } - - var recognizedTextObservations: [RecognizedTextObservation]? { - didSet { - photoScrollView.recognizedTextObservations = recognizedTextObservations - updateAccessibilityElements() - } - } - - var redactableCharacterObservations: [CharacterObservation] { - photoScrollView.redactableCharacterObservations - } - - public var highlighterTool: HighlighterTool { - get { return workspaceView.highlighterTool } - set(newTool) { - workspaceView.highlighterTool = newTool - } - } - - public var redactions: [Redaction] { - workspaceView.redactions - } - - public func add(_ redactions: [Redaction]) { - workspaceView.add(redactions) - } - - func redact(_ observations: [any TextObservation], joinSiblings: Bool) { - workspaceView.redact(observations, joinSiblings: joinSiblings) - updateAccessibilityElements() - } - - func unredact(_ observation: any TextObservation) { - workspaceView.unredact(observation) - updateAccessibilityElements() - } - - var seekPreviewObservations: [CharacterObservation] { - get { workspaceView.seekPreviewObservations } - set(newTextObservations) { - workspaceView.seekPreviewObservations = newTextObservations - } - } - - // MARK: UIScrollViewDelegate - - public func viewForZooming(in scrollView: UIScrollView) -> UIView? { - return workspaceView - } - - public func scrollViewDidZoom(_ scrollView: UIScrollView) { - guard scrollView == photoScrollView else { return } - workspaceView.scrollViewDidZoom(to: scrollView.zoomScale) - } - - // MARK: Accessibility - - private func updateAccessibilityElements() { - let wordObservations = recognizedTextObservations?.flatMap(\.allWordObservations) ?? [] - let accessibilityElements = wordObservations.map { observation in - WordObservationAccessibilityElement(observation, in: workspaceView) { [weak self] observation, isRedacted -> Bool in - if isRedacted { - self?.unredact(observation) - } else { - self?.redact([observation], joinSiblings: true) - } - - return true - } - } - - workspaceView.accessibilityElements = accessibilityElements - workspaceView.accessibilityCustomRotors = [RedactedWordObservationRotor(accessibilityElements: accessibilityElements)] - } - - // MARK: Boilerplate - - private let photoScrollView = PhotoEditingScrollView() - private var workspaceView: PhotoEditingWorkspaceView { photoScrollView.workspaceView } - - @available(*, unavailable) - required init(coder: NSCoder) { - let className = String(describing: type(of: self)) - fatalError("\(className) does not implement init(coder:)") - } -} diff --git a/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingViewController.swift b/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingViewController.swift index 3f2efe1b..b5012fd8 100644 --- a/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingViewController.swift +++ b/Modules/Legacy/Editing/Sources/Editing View/PhotoEditingViewController.swift @@ -565,7 +565,7 @@ public class PhotoEditingViewController: UIViewController, UIScrollViewDelegate, private var colorObserver: Any? private let imageManager = PHImageManager() private let textRectangleDetector = TextDetector() - private let photoEditingView = PhotoEditingView() + private let photoEditingView = PhotoEditingScrollView() private var redactionChangeObserver: Any? private var hideAutoRedactionsChangeObserver: Any? diff --git a/Modules/Legacy/Editing/Sources/Editing View/Workspace/PhotoEditingWorkspaceView.swift b/Modules/Legacy/Editing/Sources/Editing View/Workspace/PhotoEditingWorkspaceView.swift index bc4d01bc..f5371917 100644 --- a/Modules/Legacy/Editing/Sources/Editing View/Workspace/PhotoEditingWorkspaceView.swift +++ b/Modules/Legacy/Editing/Sources/Editing View/Workspace/PhotoEditingWorkspaceView.swift @@ -25,28 +25,22 @@ class PhotoEditingWorkspaceView: UIControl, UIGestureRecognizerDelegate { addSubview(redactionView) addSubview(brushStrokeView) - NSLayoutConstraint.activate([ - imageView.centerXAnchor.constraint(equalTo: centerXAnchor), - imageView.centerYAnchor.constraint(equalTo: centerYAnchor), - imageView.widthAnchor.constraint(equalTo: widthAnchor), - imageView.heightAnchor.constraint(equalTo: heightAnchor), - visualizationView.centerXAnchor.constraint(equalTo: centerXAnchor), - visualizationView.centerYAnchor.constraint(equalTo: centerYAnchor), - visualizationView.widthAnchor.constraint(equalTo: widthAnchor), - visualizationView.heightAnchor.constraint(equalTo: heightAnchor), - debugView.centerXAnchor.constraint(equalTo: centerXAnchor), - debugView.centerYAnchor.constraint(equalTo: centerYAnchor), - debugView.widthAnchor.constraint(equalTo: widthAnchor), - debugView.heightAnchor.constraint(equalTo: heightAnchor), - redactionView.centerXAnchor.constraint(equalTo: centerXAnchor), - redactionView.centerYAnchor.constraint(equalTo: centerYAnchor), - redactionView.widthAnchor.constraint(equalTo: widthAnchor), - redactionView.heightAnchor.constraint(equalTo: heightAnchor), - brushStrokeView.centerXAnchor.constraint(equalTo: centerXAnchor), - brushStrokeView.centerYAnchor.constraint(equalTo: centerYAnchor), - brushStrokeView.widthAnchor.constraint(equalTo: widthAnchor), - brushStrokeView.heightAnchor.constraint(equalTo: heightAnchor), - ]) + func constraints(for view: UIView) -> [NSLayoutConstraint] { + [ + view.centerXAnchor.constraint(equalTo: centerXAnchor), + view.centerYAnchor.constraint(equalTo: centerYAnchor), + view.widthAnchor.constraint(equalTo: widthAnchor), + view.heightAnchor.constraint(equalTo: heightAnchor), + ] + } + + NSLayoutConstraint.activate( + constraints(for: imageView) + + constraints(for: visualizationView) + + constraints(for: debugView) + + constraints(for: redactionView) + + constraints(for: brushStrokeView) + ) brushStrokeView.addTarget(self, action: #selector(handleStrokeBegin), for: .touchDown) brushStrokeView.addTarget(self, action: #selector(handleStrokeCompletion), for: .touchUpInside) diff --git a/Modules/Legacy/Editing/Sources/Navigation/Toolbar.swift b/Modules/Legacy/Editing/Sources/Navigation/Toolbar.swift index 43dec613..a4e5ec2b 100644 --- a/Modules/Legacy/Editing/Sources/Navigation/Toolbar.swift +++ b/Modules/Legacy/Editing/Sources/Navigation/Toolbar.swift @@ -7,7 +7,6 @@ import UIKit public class Toolbar: UIToolbar { override init(frame: CGRect) { super.init(frame: frame) - isTranslucent = false tintColor = .white standardAppearance = ToolbarAppearance() @@ -17,6 +16,12 @@ public class Toolbar: UIToolbar { scrollEdgeAppearance = ToolbarAppearance() compactScrollEdgeAppearance = ToolbarAppearance() } + + if #available(iOS 26.0, *) { + isTranslucent = true + } else { + isTranslucent = false + } } // MARK: Boilerplate diff --git a/Modules/Legacy/Editing/Tests/Editing View/PhotoEditingViewTests.swift b/Modules/Legacy/Editing/Tests/Editing View/PhotoEditingViewTests.swift index 07c61dcd..fe6e6566 100644 --- a/Modules/Legacy/Editing/Tests/Editing View/PhotoEditingViewTests.swift +++ b/Modules/Legacy/Editing/Tests/Editing View/PhotoEditingViewTests.swift @@ -10,7 +10,7 @@ class PhotoEditingViewTests: XCTestCase { try TestHelpers.runOnMacCatalyst() TestHelpers.performInLightMode { do { - let workspaceView = PhotoEditingView() + let workspaceView = PhotoEditingScrollView() let backgroundColor = try XCTUnwrap(workspaceView.backgroundColor?.hexString) print(UIColor.secondarySystemBackground.hexString) XCTAssertEqual(backgroundColor, "#ececec") @@ -24,7 +24,7 @@ class PhotoEditingViewTests: XCTestCase { try TestHelpers.runOnMacCatalyst() TestHelpers.performInDarkMode { do { - let workspaceView = PhotoEditingView() + let workspaceView = PhotoEditingScrollView() let backgroundColor = try XCTUnwrap(workspaceView.backgroundColor?.hexString) print(UIColor.secondarySystemBackground.hexString) XCTAssertEqual(backgroundColor, "#323232") @@ -38,7 +38,7 @@ class PhotoEditingViewTests: XCTestCase { try TestHelpers.skipOnMacCatalyst() TestHelpers.performInLightMode { do { - let workspaceView = PhotoEditingView() + let workspaceView = PhotoEditingScrollView() let backgroundColor = try XCTUnwrap(workspaceView.backgroundColor?.hexString) print(UIColor.secondarySystemBackground.hexString) XCTAssertEqual(backgroundColor, "#212121") @@ -52,7 +52,7 @@ class PhotoEditingViewTests: XCTestCase { try TestHelpers.skipOnMacCatalyst() TestHelpers.performInDarkMode { do { - let workspaceView = PhotoEditingView() + let workspaceView = PhotoEditingScrollView() let backgroundColor = try XCTUnwrap(workspaceView.backgroundColor?.hexString) print(UIColor.secondarySystemBackground.hexString) XCTAssertEqual(backgroundColor, "#212121")