From 32b605beed90920c89dce7a3bc136814b2c768b0 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Tue, 19 Nov 2024 16:31:27 +0300 Subject: [PATCH 1/8] Add image playground to the upload menu --- Demo/Gravatar-Demo.xcodeproj/project.pbxproj | 2 + .../SystemImagePickerView.swift | 67 +++++++++++++++---- .../GravatarUI/SwiftUI/View+Additions.swift | 27 ++++++++ 3 files changed, 83 insertions(+), 13 deletions(-) diff --git a/Demo/Gravatar-Demo.xcodeproj/project.pbxproj b/Demo/Gravatar-Demo.xcodeproj/project.pbxproj index 5893c842e..d5e25bfb6 100644 --- a/Demo/Gravatar-Demo.xcodeproj/project.pbxproj +++ b/Demo/Gravatar-Demo.xcodeproj/project.pbxproj @@ -467,6 +467,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Manual; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = PZYM8XX95Q; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Demo/Gravatar-Demo/Info.plist"; @@ -482,6 +483,7 @@ MARKETING_VERSION = 1.0; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "Gravatar Demo App Development"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Gravatar Demo App Development"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift b/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift index 63b2495b8..c395ba245 100644 --- a/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift +++ b/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift @@ -1,6 +1,8 @@ import PhotosUI import SwiftUI +import ImagePlayground + struct SystemImagePickerView: View where Label: View { @ViewBuilder var label: () -> Label var customEditor: ImageEditorBlock? @@ -22,12 +24,14 @@ private struct ImagePicker: View where Labe } @State var isPresented = false + @State var isPlaygroundPresented = false @State private var sourceType: SourceType? @ViewBuilder var label: () -> Label let onImageSelected: (UIImage) -> Void var customEditor: ImageEditorBlock? @State var imagePickerSelectedItem: ImagePickerItem? + @State var playgroundSelectedItem: ImagePickerItem? var body: some View { VStack { @@ -40,33 +44,62 @@ private struct ImagePicker: View where Labe SwiftUI.Label(source.localizedTitle, systemImage: source.iconName) } } + if #available(iOS 18.2, *) { + if EnvironmentValues().supportsImagePlayground { + Button { + isPlaygroundPresented = true + } label: { + SwiftUI.Label(ImagePickerLocalized.playgroundMenuTitle, systemImage: "apple.image.playground") + } + } + } } label: { label() } } + .imagePlaygroundSheetIfAvailable( + isPresented: $isPlaygroundPresented, + sourceImage: nil, + onCompletion: { url in + if let image = UIImage(contentsOfFile: url.relativePath) { + playgroundSelectedItem = ImagePickerItem(id: url.absoluteString, image: image) + } + }, + onCancellation: {} + ) + .sheet(item: $playgroundSelectedItem, content: { item in + imageEditor(with: item) + }) .sheet(item: $sourceType, content: { source in // This allows to present different kind of pickers for different sources. displayImagePicker(for: source) .sheet(item: $imagePickerSelectedItem, content: { item in - if let customEditor { - customEditor(item.image) { editedImage in - self.onImageEdited(editedImage) - } - } else { - ImageCropper(inputImage: item.image) { croppedImage in - Task { - await self.onImageEdited(croppedImage) - } - } onCancel: { - imagePickerSelectedItem = nil - }.ignoresSafeArea() - } + imageEditor(with: item) }) }) } + @ViewBuilder + func imageEditor(with item: ImagePickerItem) -> some View { + if let customEditor { + customEditor(item.image) { editedImage in + self.onImageEdited(editedImage) + } + } else { + ImageCropper(inputImage: item.image) { croppedImage in + Task { + await self.onImageEdited(croppedImage) + } + } onCancel: { + imagePickerSelectedItem = nil + playgroundSelectedItem = nil + }.ignoresSafeArea() + } + } + private func onImageEdited(_ image: UIImage) { imagePickerSelectedItem = nil + playgroundSelectedItem = nil sourceType = nil onImageSelected(image) } @@ -98,6 +131,14 @@ private struct ImagePicker: View where Labe } } +private enum ImagePickerLocalized { + static let playgroundMenuTitle: String = SDKLocalizedString( + "SystemImagePickerView.Source.Playground.title", + value: "Playground", + comment: "An option to show the image playground" + ) +} + extension ImagePicker.SourceType { var iconName: String { switch self { diff --git a/Sources/GravatarUI/SwiftUI/View+Additions.swift b/Sources/GravatarUI/SwiftUI/View+Additions.swift index afa7dcde0..d8706506f 100644 --- a/Sources/GravatarUI/SwiftUI/View+Additions.swift +++ b/Sources/GravatarUI/SwiftUI/View+Additions.swift @@ -119,4 +119,31 @@ extension View { } } } + + /// Conditionally applies a modifier to the view. + @ViewBuilder + func `if`( + _ condition: () -> Bool, + transform: (Self) -> some View + ) -> some View { + if condition() { + transform(self) + } else { + self + } + } + + @ViewBuilder + public func imagePlaygroundSheetIfAvailable( + isPresented: Binding, + sourceImage: Image? = nil, + onCompletion: @escaping (URL) -> Void, + onCancellation: (() -> Void)? = nil + ) -> some View { + if #available(iOS 18.2, *) { + self.imagePlaygroundSheet(isPresented: isPresented, sourceImage: sourceImage, onCompletion: onCompletion, onCancellation: onCancellation) + } else { + self + } + } } From b540220749a114479b0a7ac7da6c47ed971956c3 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 20 Nov 2024 10:26:29 +0300 Subject: [PATCH 2/8] Add playground to the enum --- .../SystemImagePickerView.swift | 63 +++++++++++-------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift b/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift index c395ba245..ba542d525 100644 --- a/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift +++ b/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift @@ -17,6 +17,17 @@ private struct ImagePicker: View where Labe enum SourceType: CaseIterable, Identifiable { case photoLibrary case camera + case playground + + static var allCases: [SourceType] { + var cases: [SourceType] = [.camera, .photoLibrary] + if #available(iOS 18.2, *) { + if EnvironmentValues().supportsImagePlayground { + cases.append(.playground) + } + } + return cases + } var id: Int { self.hashValue @@ -24,7 +35,6 @@ private struct ImagePicker: View where Labe } @State var isPresented = false - @State var isPlaygroundPresented = false @State private var sourceType: SourceType? @ViewBuilder var label: () -> Label @@ -44,21 +54,15 @@ private struct ImagePicker: View where Labe SwiftUI.Label(source.localizedTitle, systemImage: source.iconName) } } - if #available(iOS 18.2, *) { - if EnvironmentValues().supportsImagePlayground { - Button { - isPlaygroundPresented = true - } label: { - SwiftUI.Label(ImagePickerLocalized.playgroundMenuTitle, systemImage: "apple.image.playground") - } - } - } } label: { label() } } .imagePlaygroundSheetIfAvailable( - isPresented: $isPlaygroundPresented, + isPresented: Binding( + get: { sourceType == .playground }, + set: { if !$0 { sourceType = nil } } + ), sourceImage: nil, onCompletion: { url in if let image = UIImage(contentsOfFile: url.relativePath) { @@ -70,13 +74,19 @@ private struct ImagePicker: View where Labe .sheet(item: $playgroundSelectedItem, content: { item in imageEditor(with: item) }) - .sheet(item: $sourceType, content: { source in - // This allows to present different kind of pickers for different sources. - displayImagePicker(for: source) - .sheet(item: $imagePickerSelectedItem, content: { item in - imageEditor(with: item) - }) - }) + .sheet( + item: Binding( + get: { sourceType != .playground ? sourceType : nil }, + set: { sourceType = $0 } + ), + content: { source in + // This allows to present different kind of pickers for different sources. + displayImagePicker(for: source) + .sheet(item: $imagePickerSelectedItem, content: { item in + imageEditor(with: item) + }) + } + ) } @ViewBuilder @@ -120,6 +130,8 @@ private struct ImagePicker: View where Labe } onCancel: { sourceType = nil }.ignoresSafeArea() + case .playground: + EmptyView() } } @@ -146,6 +158,8 @@ extension ImagePicker.SourceType { "camera" case .photoLibrary: "photo.on.rectangle.angled" + case .playground: + "apple.image.playground" } } @@ -163,13 +177,12 @@ extension ImagePicker.SourceType { value: "Take a Photo", comment: "An option in a menu that will display the camera for taking a picture" ) - } - } - - func map() -> UIImagePickerController.SourceType { - switch self { - case .photoLibrary: .photoLibrary - case .camera: .camera + case .playground: + SDKLocalizedString( + "SystemImagePickerView.Source.Playground.title", + value: "Playground", + comment: "An option to show the image playground" + ) } } } From 12541accbf5922fc8076923f39b7220886464b85 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 20 Nov 2024 10:26:37 +0300 Subject: [PATCH 3/8] Remove unused var --- .../AvatarPicker/SystemImagePicker/SystemImagePickerView.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift b/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift index ba542d525..631498b86 100644 --- a/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift +++ b/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift @@ -34,7 +34,6 @@ private struct ImagePicker: View where Labe } } - @State var isPresented = false @State private var sourceType: SourceType? @ViewBuilder var label: () -> Label @@ -49,7 +48,6 @@ private struct ImagePicker: View where Labe ForEach(SourceType.allCases) { source in Button { sourceType = source - isPresented = true } label: { SwiftUI.Label(source.localizedTitle, systemImage: source.iconName) } From 3e0fba2e6c4ff00c550738340eaf8bb3504f48f7 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 20 Nov 2024 12:16:02 +0300 Subject: [PATCH 4/8] Remove unused --- Sources/GravatarUI/SwiftUI/View+Additions.swift | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Sources/GravatarUI/SwiftUI/View+Additions.swift b/Sources/GravatarUI/SwiftUI/View+Additions.swift index d8706506f..ab23e321f 100644 --- a/Sources/GravatarUI/SwiftUI/View+Additions.swift +++ b/Sources/GravatarUI/SwiftUI/View+Additions.swift @@ -120,19 +120,6 @@ extension View { } } - /// Conditionally applies a modifier to the view. - @ViewBuilder - func `if`( - _ condition: () -> Bool, - transform: (Self) -> some View - ) -> some View { - if condition() { - transform(self) - } else { - self - } - } - @ViewBuilder public func imagePlaygroundSheetIfAvailable( isPresented: Binding, From 4a4510caa1f3c701d93b9c8bbff70064a9cd3284 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 20 Nov 2024 12:23:49 +0300 Subject: [PATCH 5/8] Add the missing NSCameraUsageDescription --- Demo/Gravatar-Demo.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Demo/Gravatar-Demo.xcodeproj/project.pbxproj b/Demo/Gravatar-Demo.xcodeproj/project.pbxproj index d5e25bfb6..5a0e1c2dc 100644 --- a/Demo/Gravatar-Demo.xcodeproj/project.pbxproj +++ b/Demo/Gravatar-Demo.xcodeproj/project.pbxproj @@ -471,6 +471,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Demo/Gravatar-Demo/Info.plist"; + INFOPLIST_KEY_NSCameraUsageDescription = "Accessing camera to create an avatar."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; @@ -500,6 +501,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Demo/Gravatar-Demo/Info.plist"; + INFOPLIST_KEY_NSCameraUsageDescription = "Accessing camera to create an avatar."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; From aff6072fd0a6cba6bbf11fd7386a0fc93faa7f13 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 20 Nov 2024 12:31:06 +0300 Subject: [PATCH 6/8] Replace import --- .../AvatarPicker/SystemImagePicker/SystemImagePickerView.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift b/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift index 631498b86..1c0cf1d08 100644 --- a/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift +++ b/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift @@ -1,8 +1,7 @@ +import ImagePlayground import PhotosUI import SwiftUI -import ImagePlayground - struct SystemImagePickerView: View where Label: View { @ViewBuilder var label: () -> Label var customEditor: ImageEditorBlock? From c58a069cbd90c3d345ccabc3183b7dd7d108a9a3 Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 20 Nov 2024 12:43:08 +0300 Subject: [PATCH 7/8] Remove unused --- .../SystemImagePicker/SystemImagePickerView.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift b/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift index 1c0cf1d08..f98c3b803 100644 --- a/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift +++ b/Sources/GravatarUI/SwiftUI/AvatarPicker/SystemImagePicker/SystemImagePickerView.swift @@ -140,14 +140,6 @@ private struct ImagePicker: View where Labe } } -private enum ImagePickerLocalized { - static let playgroundMenuTitle: String = SDKLocalizedString( - "SystemImagePickerView.Source.Playground.title", - value: "Playground", - comment: "An option to show the image playground" - ) -} - extension ImagePicker.SourceType { var iconName: String { switch self { From 2635ee4c2bc56503b02d7a21b31d839e05a7b98e Mon Sep 17 00:00:00 2001 From: Pinar Olguc Date: Wed, 20 Nov 2024 12:45:13 +0300 Subject: [PATCH 8/8] Update strings in base locale --- Sources/GravatarUI/Resources/en.lproj/Localizable.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/GravatarUI/Resources/en.lproj/Localizable.strings b/Sources/GravatarUI/Resources/en.lproj/Localizable.strings index 796713b5a..6406cfa2c 100644 --- a/Sources/GravatarUI/Resources/en.lproj/Localizable.strings +++ b/Sources/GravatarUI/Resources/en.lproj/Localizable.strings @@ -106,3 +106,6 @@ /* An option in a menu that display the user's Photo Library and allow them to choose a photo from it */ "SystemImagePickerView.Source.PhotoLibrary.title" = "Choose a Photo"; +/* An option to show the image playground */ +"SystemImagePickerView.Source.Playground.title" = "Playground"; +