Skip to content

Commit

Permalink
Merge branch '197-mac-app-does-not-save' into 'release/21.7'
Browse files Browse the repository at this point in the history
Resolve "Mac app does not save"

Closes #197

See merge request highlighter/app!174
  • Loading branch information
Arclite committed Sep 28, 2021
2 parents 977e8f0 + 1c10d01 commit bbb4c7c
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 22 deletions.
16 changes: 11 additions & 5 deletions Editing/Editing View/PhotoEditingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ open class PhotoEditingViewController: UIViewController, UIScrollViewDelegate, U
hasMadeEdits = true
}

func clearHasMadeEdits() {
public func clearHasMadeEdits() {
hasMadeEdits = false
}

Expand Down Expand Up @@ -186,9 +186,9 @@ open class PhotoEditingViewController: UIViewController, UIScrollViewDelegate, U
}

#if targetEnvironment(macCatalyst)
if action == #selector(save(_:)) {
return self.canSave
}
// if action == #selector(PhotoEditingViewController.save(_:)) {
// return self.canSave
// }
#endif

return super.canPerformAction(action, withSender: sender)
Expand Down Expand Up @@ -240,8 +240,14 @@ open class PhotoEditingViewController: UIViewController, UIScrollViewDelegate, U
guard let editingActivity = (activity as? EditingUserActivity) else { return }
if let asset = asset {
editingActivity.assetLocalIdentifier = asset.localIdentifier
} else if let representedURL = fileURLProvider?.representedFileURL {
let accessGranted = representedURL.startAccessingSecurityScopedResource()
defer { representedURL.stopAccessingSecurityScopedResource() }
guard accessGranted else { return }

editingActivity.imageBookmarkData = try? representedURL.bookmarkData()
} else if let image = image {
imageCache.writeImageToCache(image, fileName: fileNameProvider?.representedFileName) { result in
imageCache.writeImageToCache(image, fileName: fileURLProvider?.representedFileName) { result in
guard let url = try? result.get() else { return }
editingActivity.imageBookmarkData = try? url.bookmarkData()
}
Expand Down
2 changes: 1 addition & 1 deletion Editing/Navigation/NavigationBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class NavigationBar: UINavigationBar {
standardAppearance = NavigationBarAppearance()
compactAppearance = NavigationBarAppearance()
scrollEdgeAppearance = NavigationBarAppearance()
#if swift(>=5.5)
#if swift(>=5.5) && !targetEnvironment(macCatalyst)
if #available(iOS 15.0, *) {
compactScrollEdgeAppearance = NavigationBarAppearance()
}
Expand Down
16 changes: 10 additions & 6 deletions Editing/RestorationImageCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,20 @@ public class RestorationImageCache: NSObject {
}
}

public protocol FileNameProvider {
var representedFileName: String? { get }
public protocol FileURLProvider {
var representedFileURL: URL? { get }
}

extension UIResponder {
var fileNameProvider: FileNameProvider? {
if let provider = (self as? FileNameProvider) {
extension FileURLProvider {
var representedFileName: String? { representedFileURL?.lastPathComponent }
}

public extension UIResponder {
var fileURLProvider: FileURLProvider? {
if let provider = (self as? FileURLProvider) {
return provider
}

return next?.fileNameProvider
return next?.fileURLProvider
}
}
4 changes: 2 additions & 2 deletions Highlighter.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@
0493BA93253AA4F80083198C /* LimitedLibraryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0493BA92253AA4F80083198C /* LimitedLibraryButton.swift */; };
0493D2D624B6BAE400FF99F4 /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0493D2D524B6BAE400FF99F4 /* ColorPickerViewController.swift */; };
0493D2D824B6BD0000FF99F4 /* BrushStampFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0493D2D724B6BD0000FF99F4 /* BrushStampFactory.swift */; };
04953B5824E2405500877800 /* PhotoEditingViewController+Desktop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04953B5724E2405500877800 /* PhotoEditingViewController+Desktop.swift */; };
04975D1E26106D6800AA2771 /* PhotoEditingCanvasBrushStrokeView.h in Headers */ = {isa = PBXBuildFile; fileRef = 04A8A5EB2316589E006CE411 /* PhotoEditingCanvasBrushStrokeView.h */; settings = {ATTRIBUTES = (Public, ); }; };
04975D3726106D8700AA2771 /* PhotoEditingCanvasView.h in Headers */ = {isa = PBXBuildFile; fileRef = 04A8A5EF231659BA006CE411 /* PhotoEditingCanvasView.h */; settings = {ATTRIBUTES = (Public, ); }; };
04975D5826106DC100AA2771 /* PhotoEditingCanvasBrushStrokeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A8A5EC2316589E006CE411 /* PhotoEditingCanvasBrushStrokeView.m */; };
Expand Down Expand Up @@ -216,6 +215,7 @@
049F930822CDAB570090C9BC /* Aleo-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 041EFF572255A2EE0058D8EE /* Aleo-Regular.otf */; };
049F930922CDAB570090C9BC /* Aleo-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 041EFF562255A2ED0058D8EE /* Aleo-Bold.otf */; };
049F930A22CDAB570090C9BC /* Aleo-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 041EFF572255A2EE0058D8EE /* Aleo-Regular.otf */; };
04A280DE26F2EA35002A0A32 /* PhotoEditingViewController+Desktop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04953B5724E2405500877800 /* PhotoEditingViewController+Desktop.swift */; };
04A606E1251C471F007A2B82 /* DesktopAutoRedactionsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04A606E0251C471F007A2B82 /* DesktopAutoRedactionsListViewController.swift */; };
04AA1D6C252EBBE400B486C7 /* ColorPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AA1D6B252EBBE400B486C7 /* ColorPanel.swift */; };
04ABC8DD2528207400F5BA6A /* CollectionPresenting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04ABC8DC2528207400F5BA6A /* CollectionPresenting.swift */; };
Expand Down Expand Up @@ -2144,7 +2144,6 @@
04BDC4AF255657CB00BCE982 /* ShortcutsRedactExporter.swift in Sources */,
046C4069247F60930069E8FB /* AlbumsHeaderLabel.swift in Sources */,
04750EA026DDE24D00E98BB4 /* AlbumsRowSelectedViewModifier.swift in Sources */,
04953B5824E2405500877800 /* PhotoEditingViewController+Desktop.swift in Sources */,
04CBB0992658AF5800B792A2 /* LogPublisher.swift in Sources */,
047E912E26560A5D0075F6B2 /* SettingsNavigationLink.swift in Sources */,
047E9140265613120075F6B2 /* PurchaseButton.swift in Sources */,
Expand All @@ -2154,6 +2153,7 @@
041CFA982665916900042E8E /* PhotoLibraryItem.swift in Sources */,
041EFF4E225302CC0058D8EE /* IntroView.swift in Sources */,
04FF8DB322F56E31008CF0E9 /* AutoRedactionsEditViewController.swift in Sources */,
04A280DE26F2EA35002A0A32 /* PhotoEditingViewController+Desktop.swift in Sources */,
047E912626549BFF0075F6B2 /* PurchaseMarketingView.swift in Sources */,
041EFF74225C3DD20058D8EE /* AssetPhotoLibraryViewCell.swift in Sources */,
04CBB0932658A4D500B792A2 /* PurchaseConstants.swift in Sources */,
Expand Down
6 changes: 4 additions & 2 deletions Highlighter/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
guard builder.system == .main else { return }

let saveCommand = UIKeyCommand(title: Self.saveMenuItemTitle, action: #selector(PhotoEditingViewController.save(_:)), input: "S", modifierFlags: [.command])
let saveAsCommand = UIKeyCommand(title: Self.saveAsMenuItemTitle, action: #selector(PhotoEditingViewController.saveAs(_:)), input: "S", modifierFlags: [.command, .shift])

if let closeMenu = builder.menu(for: .close) {
let existingChildren = closeMenu.children
let newChildren = existingChildren + [saveCommand]
let newChildren = existingChildren + [saveCommand, saveAsCommand]
let newCloseMenu = closeMenu.replacingChildren(newChildren)
builder.replace(menu: .close, with: newCloseMenu)
} else {
let saveMenu = UIMenu(title: "", options: [.displayInline], children: [
saveCommand
saveCommand, saveAsCommand
])
builder.insertSibling(saveMenu, afterMenu: .close)
}
Expand All @@ -100,6 +101,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}

private static let saveMenuItemTitle = NSLocalizedString("AppDelegate.saveMenuTitle", comment: "Save menu title")
private static let saveAsMenuItemTitle = NSLocalizedString("AppDelegate.saveAsMenuTitle", comment: "Save As menu title")

private static let preferencesMenuTitle = NSLocalizedString("AppDelegate.preferencesMenuTitle", comment: "Preferences menu title")
private static let preferencesMenuItemTitle = NSLocalizedString("AppDelegate.preferencesMenuItemTitle", comment: "Preferences menu item title")
Expand Down
13 changes: 9 additions & 4 deletions Highlighter/Desktop/DesktopViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import ErrorHandling
import UIKit

#if targetEnvironment(macCatalyst)
class DesktopViewController: UIViewController, FileNameProvider {
class DesktopViewController: UIViewController, FileURLProvider {
var editingViewController: PhotoEditingViewController? { children.first as? PhotoEditingViewController }

init(representedURL: URL?, redactions: [Redaction]?) {
Expand All @@ -17,8 +17,13 @@ class DesktopViewController: UIViewController, FileNameProvider {

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard children.contains(where: { $0 is PhotoEditingViewController }) == false else { return }
guard representedURL == nil else { return loadRepresentedURL() }

if children.contains(where: { $0 is PhotoEditingViewController }), let representedURL = representedURL {
windowScene?.titlebar?.representedURL = representedURL
windowScene?.title = representedURL.lastPathComponent
} else {
loadRepresentedURL()
}
}

// MARK: Represented URL
Expand Down Expand Up @@ -52,7 +57,7 @@ class DesktopViewController: UIViewController, FileNameProvider {
}
}

var representedFileName: String? { return representedURL?.lastPathComponent }
var representedFileURL: URL? { representedURL }

private func validateAllToolbarItems() {
windowScene?.titlebar?.toolbar?.visibleItems?.forEach { $0.validate() }
Expand Down
9 changes: 9 additions & 0 deletions Highlighter/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"AppDelegate.preferencesMenuTitle" = "Preferences";
"AppDelegate.preferencesMenuItemTitle" = "Preferences…";
"AppDelegate.saveMenuTitle" = "Save";
"AppDelegate.saveAsMenuTitle" = "Save As…";

"AutoRedactionsAdditionDialogFactory.addButtonTitle" = "Add Word";
"AutoRedactionsAdditionDialogFactory.placeholder" = "Hidden Word";
Expand All @@ -33,6 +34,14 @@

"ContactMailViewController.emailSubject" = "Hello!";

"DesktopSaveAlertController.dismissButtonTitle" = "OK";

"DesktopSaveError.missingRepresentedURL.alertTitle" = "Invalid Image";
"DesktopSaveError.missingImageType.alertTitle" = "Unknown Image Type";
"DesktopSaveError.noImageData.alertTitle" = "Export Failed";

"DesktopSaveError.alertMessage" = "There was an unexpected error when saving the image. If this continues to happen, please contact support at [email protected].";

"DesktopSettingsSceneDelegate.windowTitle" = "Preferences";

"DesktopSettingsView.wordListLabel" = "Auto-Hidden Words";
Expand Down
96 changes: 94 additions & 2 deletions Highlighter/Photo Editing/PhotoEditingViewController+Desktop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ extension PhotoEditingViewController {
}

@objc func save(_ sender: Any) {
guard let exportURL = view.window?.windowScene?.titlebar?.representedURL, let imageType = imageType else { return }
guard let exportURL = fileURLProvider?.representedFileURL else { return present(.missingRepresentedURL) }
guard let imageType = imageType else { return present(.missingImageType) }

exportImage { [weak self] image in
let data: Data?
Expand All @@ -28,7 +29,12 @@ extension PhotoEditingViewController {
data = image?.pngData()
}

guard let exportData = data else { return }
guard let exportData = data else {
DispatchQueue.main.async { [weak self] in
self?.present(.noImageData)
}
return
}
do {
try exportData.write(to: exportURL)
self?.clearHasMadeEdits()
Expand All @@ -41,10 +47,96 @@ extension PhotoEditingViewController {
}
}

@objc func saveAs(_ sender: Any) {
guard let representedURL = fileURLProvider?.representedFileURL else { return present(.missingRepresentedURL) }
guard let imageType = imageType else { return present(.missingImageType) }

let temporaryURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(representedURL.lastPathComponent)

exportImage { [weak self] image in
let data: Data?

switch imageType {
case .jpeg:
data = image?.jpegData(compressionQuality: 0.9)
case .png: fallthrough
default:
data = image?.pngData()
}

guard let exportData = data else {
DispatchQueue.main.async { [weak self] in
self?.present(.noImageData)
}
return
}
do {
try exportData.write(to: temporaryURL)
self?.clearHasMadeEdits()

Defaults.numberOfSaves += 1
DispatchQueue.main.async { [weak self] in
let saveViewController = DesktopSaveViewController(url: temporaryURL) { [weak self] in
AppRatingsPrompter.displayRatingsPrompt(in: self?.view.window?.windowScene)
}
self?.present(saveViewController, animated: true, completion: nil)
}
} catch {}
}
}

private func present(_ error: DesktopSaveError) {
present(DesktopSaveAlertController(error: error), animated: true, completion: nil)
}

var canSave: Bool {
guard let imageType = imageType else { return false }
guard hasMadeEdits == true else { return false }
return [UTType.png, .jpeg].contains(imageType)
}
}

class DesktopSaveViewController: UIDocumentPickerViewController, UIDocumentPickerDelegate {
private var onSave: (() -> Void)? = nil
convenience init(url: URL, onSave: @escaping (() -> Void)) {
self.init(forExporting: [url], asCopy: true)
self.onSave = onSave
delegate = self
}

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let onSave = self.onSave else { return }
DispatchQueue.main.async {
onSave()
}
}
}

enum DesktopSaveError: Error {
case missingRepresentedURL
case missingImageType
case noImageData

var alertTitle: String {
switch self {
case .missingRepresentedURL: return NSLocalizedString("DesktopSaveError.missingRepresentedURL.alertTitle", comment: "Title for the missing represented URL alert")
case .missingImageType: return NSLocalizedString("DesktopSaveError.missingImageType.alertTitle", comment: "Title for the missing image type alert")
case .noImageData: return NSLocalizedString("DesktopSaveError.noImageData.alertTitle", comment: "Title for the no export data alert")
}
}

var alertMessage: String {
return NSLocalizedString("DesktopSaveError.alertMessage", comment: "Message for the missing represented URL alert")
}
}

class DesktopSaveAlertController: UIAlertController {
convenience init(error: DesktopSaveError) {
self.init(title: error.alertTitle, message: error.alertMessage, preferredStyle: .alert)
addAction(UIAlertAction(title: Self.dismissButtonTitle, style: .default, handler: nil))
}

private static let dismissButtonTitle = NSLocalizedString("DesktopSaveAlertController.dismissButtonTitle", comment: "Dismiss button for the save error alert")
}
#endif

0 comments on commit bbb4c7c

Please sign in to comment.