From 7eccd73b4373279b324119265b8c366a6670312e Mon Sep 17 00:00:00 2001 From: Gabriele Marcato Date: Thu, 10 Nov 2022 10:13:30 +0100 Subject: [PATCH 1/2] feat: add videos --- Package.swift | 4 +- README.md | 13 +- Sources/ImagePickerModule/ImagePicker.swift | 62 ------ .../ImagePickerModule/ImagePickerButton.swift | 206 ------------------ .../ImagePickerModuleDemo.swift | 111 ---------- .../ImagesPickerButton.swift | 105 --------- Sources/ImagePickerModule/MediaPicker.swift | 87 ++++++++ .../MediaPickerModuleDemo.swift | 100 +++++++++ .../MediasPickerButton.swift | 115 ++++++++++ .../SingleMediaPickerButton.swift | 201 +++++++++++++++++ 10 files changed, 513 insertions(+), 491 deletions(-) delete mode 100644 Sources/ImagePickerModule/ImagePicker.swift delete mode 100644 Sources/ImagePickerModule/ImagePickerButton.swift delete mode 100644 Sources/ImagePickerModule/ImagePickerModuleDemo.swift delete mode 100644 Sources/ImagePickerModule/ImagesPickerButton.swift create mode 100644 Sources/ImagePickerModule/MediaPicker.swift create mode 100644 Sources/ImagePickerModule/MediaPickerModuleDemo.swift create mode 100644 Sources/ImagePickerModule/MediasPickerButton.swift create mode 100644 Sources/ImagePickerModule/SingleMediaPickerButton.swift diff --git a/Package.swift b/Package.swift index def7a00..b347b59 100644 --- a/Package.swift +++ b/Package.swift @@ -10,13 +10,13 @@ let package = Package( .library( name: "ImagePickerModule", targets: ["ImagePickerModule"] - ) + ), ], targets: [ .target(name: "ImagePickerModule"), .testTarget( name: "ImagePickerModuleTests", dependencies: ["ImagePickerModule"] - ) + ), ] ) diff --git a/README.md b/README.md index 5b07358..629e8e7 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,18 @@ ## Usage You have 3 options to make use of the Image Picker Module in your SwiftUI project: -1. Use plain ImagePicker which has the parameters soruce type and a completion function, which returns the selected picture or taken photo -2. Use the ImagePickerButton which takes a binding of an optional UIImage and lets you define a label for the button. The Button itself comes with some functionality, which will be expanded in the future. For example does it detect if a photo was taken and shows you the option to remove it or show it in full screen. -3. Use the ImagesPickerButton, which is supposed to expand image collections and there for takes a UIImage Array Binding and again lets you set its label. +1. Use plain MediaPicker which has the parameters source type and a completion function, which returns the selected picture or taken photo/video +2. Use the SingleMediaPickerButton which takes a binding of an optional UIImage and URL and lets you define a label for the button. + The Button itself comes with some functionality, which will be expanded in the future. + For example does it detect if a photo/video was taken and shows you the option to remove it or show it in full screen. +3. Use the MediasPickerButton, which is supposed to expand image/video collections and there for takes a UIImage Array Binding/ URL Array Binding and again lets you set its label.
## Integration 1. Copy the resource url: ``` -https://github.com/swiftui-packages/image-picker-module.git +https://github.com/Kuama-IT/image-picker-module.git ``` 2. Open your Xcode project @@ -45,7 +47,8 @@ https://github.com/swiftui-packages/image-picker-module.git
## required info.plist entries -- _Privacy - Camera Usage Description_ +- _Privacy - Camera Usage Description_ +- _Privacy - Microphone Usage Description_
diff --git a/Sources/ImagePickerModule/ImagePicker.swift b/Sources/ImagePickerModule/ImagePicker.swift deleted file mode 100644 index 9681692..0000000 --- a/Sources/ImagePickerModule/ImagePicker.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// ImagePicker.swift -// ImagePickerModule -// -// Created by Cem Yilmaz on 20.08.21. -// - -import SwiftUI - -#if canImport(UIKit) -public struct ImagePicker: UIViewControllerRepresentable { - private let sourceType: UIImagePickerController.SourceType - private let onImagePicked: (UIImage) -> Void - @Environment(\.presentationMode) private var presentationMode: Binding - - public init(sourceType: UIImagePickerController.SourceType, onImagePicked: @escaping (UIImage) -> Void) { - self.sourceType = sourceType - self.onImagePicked = onImagePicked - } - - public func makeUIViewController(context: Context) -> UIImagePickerController { - let picker = UIImagePickerController() - picker.sourceType = self.sourceType - picker.delegate = context.coordinator - picker.allowsEditing = true - return picker - } - - public func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {} - - public func makeCoordinator() -> Coordinator { - Coordinator( - onDismiss: { self.presentationMode.wrappedValue.dismiss() }, - onImagePicked: self.onImagePicked - ) - } - - final public class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { - private let onDismiss: () -> Void - private let onImagePicked: (UIImage) -> Void - - init(onDismiss: @escaping () -> Void, onImagePicked: @escaping (UIImage) -> Void) { - self.onDismiss = onDismiss - self.onImagePicked = onImagePicked - } - - public func imagePickerController( - _ picker: UIImagePickerController, - didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any] - ) { - if let image = info[.editedImage] as? UIImage { - self.onImagePicked(image) - } - self.onDismiss() - } - - public func imagePickerControllerDidCancel(_: UIImagePickerController) { - self.onDismiss() - } - } -} -#endif diff --git a/Sources/ImagePickerModule/ImagePickerButton.swift b/Sources/ImagePickerModule/ImagePickerButton.swift deleted file mode 100644 index 0e8436d..0000000 --- a/Sources/ImagePickerModule/ImagePickerButton.swift +++ /dev/null @@ -1,206 +0,0 @@ -// -// ImagePickerButton.swift -// ImagePickerModule -// -// Created by Cem Yilmaz on 20.08.21. -// - -import SwiftUI -import AVFoundation - -#if canImport(UIKit) -public struct ImagePickerButton: View { - - public enum NoCameraAccessStrategy { - case hideOption, showToSettings - } - - private let noCameraAccessStrategy: NoCameraAccessStrategy - - @Binding private var selectedImage: UIImage? - private let label: Content - - private let onDelete: (() -> Void)? - private let defaultImageContent: (() -> DefaultImageContent)? - - @State private var showCameraImagePicker: Bool = false - @State private var showLibraryImagePicker: Bool = false - @State private var showSelectedImage: Bool = false - @State private var showCameraAccessRequiredAlert: Bool = false - - public var body: some View { - - ZStack { - - Menu( - - content: { - - Button( - action: { self.showLibraryImagePicker = true }, - label: { - Label(NSLocalizedString("Foto aus Bibliothek auswählen", comment: ""), systemImage: "folder") - } - ) - - if AVCaptureDevice.authorizationStatus(for: .video) == .authorized || - AVCaptureDevice.authorizationStatus(for: .video) == .notDetermined { - - Button( - action: { self.showCameraImagePicker = true }, - label: { Label(NSLocalizedString("Foto mit Kamera aufnehmen", comment: ""), systemImage: "camera") } - ) - - } else if AVCaptureDevice.authorizationStatus(for: .video) == .denied { - - if self.noCameraAccessStrategy == NoCameraAccessStrategy.showToSettings { - Button( - action: { self.showCameraAccessRequiredAlert = true }, - label: { - Label( - NSLocalizedString("Foto mit Kamera aufnehmen", comment: ""), - systemImage: "camera" - ) - } - ) - } - - } - - if self.defaultImageContent != nil || self.selectedImage != nil { - - Button( - action: { self.showSelectedImage = true }, - label: { - Label( - NSLocalizedString("Aktuelles Bild anzeigen", comment: ""), - systemImage: "arrow.up.backward.and.arrow.down.forward" - ) - } - ) - - } - - if let onDelete = self.onDelete { - - Button( - action: { - onDelete() - self.selectedImage = nil - }, - label: { - Label( - NSLocalizedString("Aktuelles Bild entfernen", comment: ""), - systemImage: "xmark" - ) - } - ) - - } - - }, - - label: { - - if let defaultImageContent = self.defaultImageContent, self.selectedImage == nil { - defaultImageContent() - } else { - self.label - } - - } - - ).textCase(nil) - - Text("").sheet(isPresented: self.$showCameraImagePicker) { - - ImagePicker(sourceType: UIImagePickerController.SourceType.camera) { image in - self.selectedImage = image - }.ignoresSafeArea() - - } - - Text("").sheet(isPresented: self.$showLibraryImagePicker) { - - ImagePicker(sourceType: .photoLibrary) { image in - self.selectedImage = image - }.ignoresSafeArea() - - } - - Text("").sheet(isPresented: self.$showSelectedImage) { - - if let defaultImageContent = self.defaultImageContent { - - defaultImageContent() - - } else if let profilePicture = self.selectedImage { - - Image(uiImage: profilePicture) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxWidth: .infinity) - - } - - } - - Text("").alert(isPresented: self.$showCameraAccessRequiredAlert) { - Alert( - title: Text(NSLocalizedString("Kamerazugriff benötigt", comment: "")), - message: Text(NSLocalizedString("Der Kamerazugriff kann in den Systemeinstellungen für diese App gewährt werden.", comment: "")), - primaryButton: Alert.Button.default(Text(NSLocalizedString("Einstellungen", comment: ""))) { - guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } - UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil) - }, - secondaryButton: Alert.Button.cancel() - ) - } - - } - - } - -} - - - -extension ImagePickerButton { - - public init( - selectedImage: Binding, - noCameraAccessStrategy: NoCameraAccessStrategy = NoCameraAccessStrategy.showToSettings, - label: @escaping () -> Content, - onDelete: (() -> Void)? = nil, - defaultImageContent: (() -> DefaultImageContent)? - ) { - self._selectedImage = selectedImage - self.noCameraAccessStrategy = noCameraAccessStrategy - self.onDelete = onDelete - self.label = label() - self.defaultImageContent = defaultImageContent - } - -} - - - -extension ImagePickerButton where DefaultImageContent == EmptyView { - - public init( - selectedImage: Binding, - noCameraAccessStrategy: NoCameraAccessStrategy = NoCameraAccessStrategy.showToSettings, - label: @escaping () -> Content, - onDelete: (() -> Void)? = nil - ) { - self.init( - selectedImage: selectedImage, - noCameraAccessStrategy: noCameraAccessStrategy, - label: label, - onDelete: onDelete, - defaultImageContent: nil - ) - } - -} -#endif diff --git a/Sources/ImagePickerModule/ImagePickerModuleDemo.swift b/Sources/ImagePickerModule/ImagePickerModuleDemo.swift deleted file mode 100644 index c442d75..0000000 --- a/Sources/ImagePickerModule/ImagePickerModuleDemo.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// ImagePickerModuleDemo.swift -// ImagePickerModule -// -// Created by Cem Yilmaz on 20.08.21. -// - -import SwiftUI - -#if canImport(UIKit) -public struct ImagePickerDemo: View { - - @State private var selectedImage: UIImage? - @State private var selectedImages: [UIImage] = [] - - public var body: some View { - - List { - - Section(header: Text("Regular Image Picker")) { - // ImagePicker(sourceType: .camera, onImagePicked: { image in }) - // ImagePicker(sourceType: .photoLibrary, onImagePicked: { image in }) - // ImagePicker(sourceType: .savedPhotosAlbum, onImagePicked: { image in }) - } - -// if let selectedImage = self.selectedImage { -// -// Section { -// -// Image(uiImage: selectedImage) -// .resizable() -// .scaledToFit() -// -// } -// -// } - - Section(header: Text("Image Picker Button")) { - ImagePickerButton(selectedImage: self.$selectedImage) { - Image(systemName: "photo") - } - - ImagePickerButton(selectedImage: self.$selectedImage, noCameraAccessStrategy: .hideOption) { - Image(systemName: "photo") - } - - ImagePickerButton(selectedImage: self.$selectedImage, noCameraAccessStrategy: .showToSettings) { - Image(systemName: "photo") - } - - ImagePickerButton( - selectedImage: self.$selectedImage, - label: { Image(systemName: "photo") }, - defaultImageContent: nil != nil ? nil : { Text("Default") } - ) - - ImagePickerButton( - selectedImage: self.$selectedImage, - label: { Image(systemName: "photo") }, - onDelete: {}, - defaultImageContent: { Image(systemName: "photo") } - ) - - ImagePickerButton( - selectedImage: self.$selectedImage, - noCameraAccessStrategy: .hideOption, - label: { Image(systemName: "photo") }, - onDelete: {}, - defaultImageContent: { Image(systemName: "photo") } - ) - } - - Section(header: Text("Images Picker Button")) { - ImagesPickerButton(selectedImages: self.$selectedImages) { - Image(systemName: "plus") - } - - ScrollView(.horizontal) { - HStack { - ForEach(self.selectedImages, id: \.self) { image in - Image(uiImage: image) - .resizable() - .frame(width: 48, height: 48) - .cornerRadius(8) - } - } - }.padding(.vertical) - - // ImagesPickerButton( - // selectedImages: <#T##Binding<[UIImage]>#>, - // noCameraAccessStrategy: <#T##NoCameraAccessStrategy#>, - // label: <#T##() -> _#> - // ) - } - - }.listStyle(GroupedListStyle()) - - } - -} - -public struct ImagePicker_Previews: PreviewProvider { - - public static var previews: some View { - - ImagePickerDemo() - - } - -} -#endif diff --git a/Sources/ImagePickerModule/ImagesPickerButton.swift b/Sources/ImagePickerModule/ImagesPickerButton.swift deleted file mode 100644 index d509c26..0000000 --- a/Sources/ImagePickerModule/ImagesPickerButton.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// ImagesPickerButton.swift -// ImagePickerModule -// -// Created by Cem Yilmaz on 20.08.21. -// - -import SwiftUI -import AVFoundation - -#if canImport(UIKit) -public struct ImagesPickerButton: View { - - public enum NoCameraAccessStrategy { - case hideOption, showToSettings - } - - @Binding public var selectedImages: [UIImage] - private let noCameraAccessStrategy: NoCameraAccessStrategy - public let label: Content - - @State private var showCameraImagePicker: Bool = false - @State private var showLibraryImagePicker: Bool = false - @State private var showCameraAccessRequiredAlert: Bool = false - - public init( - selectedImages: Binding<[UIImage]>, - noCameraAccessStrategy: NoCameraAccessStrategy = NoCameraAccessStrategy.showToSettings, - @ViewBuilder label: @escaping () -> Content - ) { - self._selectedImages = selectedImages - self.noCameraAccessStrategy = noCameraAccessStrategy - self.label = label() - } - - public var body: some View { - - ZStack { - - Menu( - - content: { - - Button( - action: { self.showLibraryImagePicker = true }, - label: { - Label(NSLocalizedString("Foto aus Bibliothek auswählen", comment: ""), systemImage: "folder") - } - ) - - if AVCaptureDevice.authorizationStatus(for: .video) == .authorized || - AVCaptureDevice.authorizationStatus(for: .video) == .notDetermined { - - Button( - action: { self.showCameraImagePicker = true }, - label: { Label(NSLocalizedString("Foto mit Kamera aufnehmen", comment: ""), systemImage: "camera") } - ) - - } else if AVCaptureDevice.authorizationStatus(for: .video) == .denied { - - if self.noCameraAccessStrategy == NoCameraAccessStrategy.showToSettings { - Button( - action: { self.showCameraAccessRequiredAlert = true }, - label: { Label(NSLocalizedString("Foto mit Kamera aufnehmen", comment: ""), systemImage: "camera") } - ) - } - - } - - }, - - label: { self.label } - - ).textCase(nil) - - Text("").sheet(isPresented: self.$showCameraImagePicker) { - ImagePicker(sourceType: UIImagePickerController.SourceType.camera) { image in - self.selectedImages.append(image) - }.ignoresSafeArea() - } - - Text("").sheet(isPresented: self.$showLibraryImagePicker) { - ImagePicker(sourceType: .photoLibrary) { image in - self.selectedImages.append(image) - }.ignoresSafeArea() - } - - Text("").alert(isPresented: self.$showCameraAccessRequiredAlert) { - Alert( - title: Text(NSLocalizedString("Kamerazugriff benötigt", comment: "")), - message: Text(NSLocalizedString("Der Kamerazugriff kann in den Systemeinstellungen für diese App gewährt werden.", comment: "")), - primaryButton: Alert.Button.default(Text(NSLocalizedString("Einstellungen", comment: ""))) { - guard let settingsULR = URL(string: UIApplication.openSettingsURLString) else { return } - UIApplication.shared.open(settingsULR, options: [:], completionHandler: nil) - }, - secondaryButton: Alert.Button.cancel() - ) - } - - } - - } - -} -#endif diff --git a/Sources/ImagePickerModule/MediaPicker.swift b/Sources/ImagePickerModule/MediaPicker.swift new file mode 100644 index 0000000..971789a --- /dev/null +++ b/Sources/ImagePickerModule/MediaPicker.swift @@ -0,0 +1,87 @@ +// +// MediaPicker.swift +// ImagePickerModule +// +// Created by Cem Yilmaz on 20.08.21. +// + +import SwiftUI + +#if canImport(UIKit) + public struct MediaPicker: UIViewControllerRepresentable { + private let sourceType: UIImagePickerController.SourceType + private let onImagePicked: (UIImage) -> Void + private let onVideoPicked: (URL) -> Void + // if true, the user can edit its selected media, but he is not allowed to select more than once + // default init is false + private let allowsEditing: Bool + @Environment(\.presentationMode) private var presentationMode: Binding + + public init(sourceType: UIImagePickerController.SourceType, + onImagePicked: @escaping (UIImage) -> Void, + onVideoPicked: @escaping (URL) -> Void, + allowsEditing: Bool = false) + { + self.sourceType = sourceType + self.onImagePicked = onImagePicked + self.onVideoPicked = onVideoPicked + self.allowsEditing = allowsEditing + } + + public func makeUIViewController(context: Context) -> UIImagePickerController { + let picker = UIImagePickerController() + picker.sourceType = sourceType + // both photos and videos + picker.mediaTypes = ["public.image", "public.movie"] + picker.delegate = context.coordinator + picker.allowsEditing = allowsEditing + return picker + } + + public func updateUIViewController(_: UIImagePickerController, context _: Context) {} + + public func makeCoordinator() -> Coordinator { + Coordinator( + onDismiss: { self.presentationMode.wrappedValue.dismiss() }, + onImagePicked: onImagePicked, + onVideoPicked: onVideoPicked, + allowsEditing: allowsEditing + ) + } + + public final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { + private let onDismiss: () -> Void + private let onImagePicked: (UIImage) -> Void + private let onVideoPicked: (URL) -> Void + private let allowsEditing: Bool + + init(onDismiss: @escaping () -> Void, + onImagePicked: @escaping (UIImage) -> Void, + onVideoPicked: @escaping (URL) -> Void, + allowsEditing: Bool) + { + self.onDismiss = onDismiss + self.onImagePicked = onImagePicked + self.onVideoPicked = onVideoPicked + self.allowsEditing = allowsEditing + } + + public func imagePickerController( + _: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any] + ) { + if let image = info[allowsEditing ? .editedImage : .originalImage] as? UIImage { + onImagePicked(image) + } + if let videoURl = info[.mediaURL] as? URL { + onVideoPicked(videoURl) + } + onDismiss() + } + + public func imagePickerControllerDidCancel(_: UIImagePickerController) { + onDismiss() + } + } + } +#endif diff --git a/Sources/ImagePickerModule/MediaPickerModuleDemo.swift b/Sources/ImagePickerModule/MediaPickerModuleDemo.swift new file mode 100644 index 0000000..1637b4f --- /dev/null +++ b/Sources/ImagePickerModule/MediaPickerModuleDemo.swift @@ -0,0 +1,100 @@ +// +// MediaPickerDemo.swift +// ImagePickerModule +// +// Created by Cem Yilmaz on 20.08.21. +// + +import AVKit +import SwiftUI + +#if canImport(UIKit) + public struct MediaPickerDemo: View { + @State private var selectedImage: UIImage? + @State private var selectedImages: [UIImage] = [] + @State private var selectedVideo: URL? + @State private var selectedVideos: [URL] = [] + public var body: some View { + List { + Section(header: Text("Image Picker Button")) { + SingleMediaPickerButton(selectedImage: self.$selectedImage, selectedVideo: self.$selectedVideo) { + Text("no strategy") + } + + SingleMediaPickerButton(selectedImage: self.$selectedImage, selectedVideo: self.$selectedVideo, noCameraAccessStrategy: .hideOption) { + Text("hideOption strategy") + } + + SingleMediaPickerButton(selectedImage: self.$selectedImage, selectedVideo: self.$selectedVideo, noCameraAccessStrategy: .showToSettings) { + Text("showToSettings strategy") + } + + SingleMediaPickerButton( + selectedImage: self.$selectedImage, + selectedVideo: self.$selectedVideo, + label: { Image(systemName: "photo") }, + defaultImageContent: { Text("Default") } + ) + + SingleMediaPickerButton( + selectedImage: self.$selectedImage, + selectedVideo: self.$selectedVideo, + label: { Image(systemName: "photo") }, + onDelete: { + print("on delete pressed") + }, + defaultImageContent: { Text("on delete") } + ) + + SingleMediaPickerButton(selectedImage: self.$selectedImage, + selectedVideo: self.$selectedVideo, label: { Text("allow editing") }, allowsEditing: true) + } + + Section(header: Text("Images Picker Button")) { + MediasPickerButton(selectedImages: self.$selectedImages, + selectedVideos: self.$selectedVideos, + label: { + Text("multiple medias") + }, allowsEditing: false) + + ScrollView(.horizontal) { + HStack { + if selectedVideos.count > 0 { + ForEach(selectedVideos, id: \.self) { video in + VideoPlayer(player: AVPlayer(url: video)) + .frame(width: 200, height: 200) + .padding() + .cornerRadius(10.0) + .onTapGesture { + guard let index = self.selectedVideos.firstIndex(of: video) else { return } + self.selectedVideos.remove(at: index) + } + } + } + if selectedImages.count > 0 { + ForEach(selectedImages, id: \.self) { img in + Image(uiImage: img) + .resizable() + .frame(width: 200, height: 200) + .padding() + .cornerRadius(10.0) + .onTapGesture { + guard let index = self.selectedImages.firstIndex(of: img) else { return } + self.selectedImages.remove(at: index) + } + } + } + } + }.padding(.vertical) + } + + }.listStyle(GroupedListStyle()) + } + } + + public struct ImagePicker_Previews: PreviewProvider { + public static var previews: some View { + MediaPickerDemo() + } + } +#endif diff --git a/Sources/ImagePickerModule/MediasPickerButton.swift b/Sources/ImagePickerModule/MediasPickerButton.swift new file mode 100644 index 0000000..8634977 --- /dev/null +++ b/Sources/ImagePickerModule/MediasPickerButton.swift @@ -0,0 +1,115 @@ +// +// ImagesPickerButton.swift +// ImagePickerModule +// +// Created by Cem Yilmaz on 20.08.21. +// + +import AVFoundation +import SwiftUI + +#if canImport(UIKit) + public struct MediasPickerButton: View { + public enum NoPermissionsStrategy { + case hideOption, showToSettings + } + + @Binding public var selectedImages: [UIImage] + @Binding public var selectedVideos: [URL] + public private(set) var allowsEditing: Bool + private let noCameraAccessStrategy: NoPermissionsStrategy + public let label: Content + + @State private var showCameraMediaPicker: Bool = false + @State private var showLibraryMediaPicker: Bool = false + @State private var showMicrophoneAndCameraAccessRequiredAlert: Bool = false + + public init( + selectedImages: Binding<[UIImage]>, + selectedVideos: Binding<[URL]>, + noCameraAccessStrategy: NoPermissionsStrategy = NoPermissionsStrategy.showToSettings, + @ViewBuilder label: @escaping () -> Content, + allowsEditing: Bool = false + ) { + _selectedImages = selectedImages + _selectedVideos = selectedVideos + self.noCameraAccessStrategy = noCameraAccessStrategy + self.label = label() + self.allowsEditing = allowsEditing + } + + public var body: some View { + ZStack { + Menu( + content: { + Button( + action: { self.showLibraryMediaPicker = true }, + label: { + Label(NSLocalizedString("Selezionare le foto o i video dalla libreria", comment: ""), + systemImage: "folder") + } + ) + + if AVCaptureDevice.authorizationStatus(for: .video) == .authorized || + AVCaptureDevice.authorizationStatus(for: .video) == .notDetermined + { + Button( + action: { self.showCameraMediaPicker = true }, + label: { + Label(NSLocalizedString("Scattare una foto o registrare un video con la fotocamera", comment: ""), + systemImage: "camera") + } + ) + + } else if AVCaptureDevice.authorizationStatus(for: .video) == .denied { + if self.noCameraAccessStrategy == NoPermissionsStrategy.showToSettings { + Button( + action: { self.showMicrophoneAndCameraAccessRequiredAlert = true + }, + label: { + Label(NSLocalizedString("Scattare una foto o registrare un video con la fotocamera", comment: ""), + systemImage: "camera") + } + ) + } + } + + }, + + label: { self.label } + + ).textCase(nil) + + Text("").sheet(isPresented: self.$showCameraMediaPicker) { + MediaPicker(sourceType: .camera, + onImagePicked: { image in + self.selectedImages.append(image) + }, onVideoPicked: { video in + self.selectedVideos.append(video) + }, allowsEditing: allowsEditing).ignoresSafeArea() + } + + Text("").sheet(isPresented: self.$showLibraryMediaPicker) { + MediaPicker(sourceType: .photoLibrary, + onImagePicked: { image in + self.selectedImages.append(image) + }, onVideoPicked: { video in + self.selectedVideos.append(video) + }, allowsEditing: allowsEditing) + } + + Text("").alert(isPresented: self.$showMicrophoneAndCameraAccessRequiredAlert) { + Alert( + title: Text(NSLocalizedString("È necessario l'accesso alla telecamera e al microfono", comment: "")), + message: Text(NSLocalizedString("L'accesso alla fotocamera e al microfono può essere concesso nelle impostazioni di sistema di questa applicazione.", comment: "")), + primaryButton: Alert.Button.default(Text(NSLocalizedString("Impostazioni", comment: ""))) { + guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } + UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil) + }, + secondaryButton: Alert.Button.cancel() + ) + } + } + } + } +#endif diff --git a/Sources/ImagePickerModule/SingleMediaPickerButton.swift b/Sources/ImagePickerModule/SingleMediaPickerButton.swift new file mode 100644 index 0000000..ebb0a90 --- /dev/null +++ b/Sources/ImagePickerModule/SingleMediaPickerButton.swift @@ -0,0 +1,201 @@ +// +// SingleMediaPickerButton.swift +// ImagePickerModule +// +// Created by Cem Yilmaz on 20.08.21. +// + +import AVFoundation +import AVKit +import SwiftUI + +#if canImport(UIKit) + public struct SingleMediaPickerButton: View { + public enum NoPermissionsStrategy { + case hideOption, showToSettings + } + + private let noCameraAccessStrategy: NoPermissionsStrategy + + @Binding private var selectedImage: UIImage? + @Binding private var selectedVideo: URL? + public private(set) var allowsEditing: Bool + + private let label: Content + + private let onDelete: (() -> Void)? + private let defaultImageContent: (() -> DefaultImageContent)? + + @State private var showCameraMediaPicker: Bool = false + @State private var showLibraryMediaPicker: Bool = false + @State private var showSelectedImageOrVideo: Bool = false + @State private var showCameraAndMicrophoneAccessRequiredAlert: Bool = false + + init( + selectedImage: Binding, + selectedVideo: Binding, + noCameraAccessStrategy: NoPermissionsStrategy = NoPermissionsStrategy.showToSettings, + label: @escaping () -> Content, + onDelete: (() -> Void)? = nil, + defaultImageContent: (() -> DefaultImageContent)?, + allowsEditing: Bool = false + ) { + _selectedImage = selectedImage + _selectedVideo = selectedVideo + self.noCameraAccessStrategy = noCameraAccessStrategy + self.onDelete = onDelete + self.label = label() + self.defaultImageContent = defaultImageContent + self.allowsEditing = allowsEditing + } + + public var body: some View { + ZStack { + Menu( + content: { + Button( + action: { self.showLibraryMediaPicker = true }, + label: { + Label(NSLocalizedString("Selezionare la foto o video dalla libreria", comment: ""), + systemImage: "folder") + } + ) + + if AVCaptureDevice.authorizationStatus(for: .video) == .authorized || + AVCaptureDevice.authorizationStatus(for: .video) == .notDetermined + { + Button( + action: { self.showCameraMediaPicker = true }, + label: { + Label(NSLocalizedString("Scattare una foto o video con la fotocamera", comment: ""), + systemImage: "camera") + } + ) + + } else if AVCaptureDevice.authorizationStatus(for: .video) == .denied { + if self.noCameraAccessStrategy == NoPermissionsStrategy.showToSettings { + Button( + action: { + self.showCameraAndMicrophoneAccessRequiredAlert = true + }, + label: { + Label( + NSLocalizedString("Scattare una foto o video con la fotocamera", comment: ""), + systemImage: "camera" + ) + } + ) + } + } + + if self.defaultImageContent != nil || self.selectedImage != nil || self.selectedVideo != nil { + Button( + action: { self.showSelectedImageOrVideo = true }, + label: { + Label( + NSLocalizedString("Mostra l'immagine o video corrente", comment: ""), + systemImage: "arrow.up.backward.and.arrow.down.forward" + ) + } + ) + } + + if let onDelete = self.onDelete { + Button( + action: { + onDelete() + self.selectedImage = nil + self.selectedVideo = nil + }, + label: { + Label( + NSLocalizedString("Rimuovere l'immagine o video corrente", comment: ""), + systemImage: "xmark" + ) + } + ) + } + + }, + + label: { + if let defaultImageContent = self.defaultImageContent, + self.selectedImage == nil || self.selectedVideo == nil + { + defaultImageContent() + } else { + self.label + } + } + + ).textCase(nil) + + Text("").sheet(isPresented: self.$showCameraMediaPicker) { + MediaPicker(sourceType: .camera, onImagePicked: { image in + self.selectedImage = image + }, onVideoPicked: { video in + self.selectedVideo = video + }, allowsEditing: allowsEditing) + .ignoresSafeArea() + } + + Text("").sheet(isPresented: self.$showLibraryMediaPicker) { + MediaPicker(sourceType: .photoLibrary, + onImagePicked: { image in + self.selectedImage = image + }, onVideoPicked: { video in + self.selectedVideo = video + }, allowsEditing: allowsEditing) + .ignoresSafeArea() + } + + Text("").sheet(isPresented: self.$showSelectedImageOrVideo) { + if let defaultImageContent = self.defaultImageContent { + defaultImageContent() + + } else if let selectedImage { + Image(uiImage: selectedImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: .infinity) + } else if let selectedVideo { + VideoPlayer(player: AVPlayer(url: selectedVideo)) + } + } + + Text("").alert(isPresented: self.$showCameraAndMicrophoneAccessRequiredAlert) { + Alert( + title: Text(NSLocalizedString("È necessario l'accesso alla telecamera", comment: "")), + message: Text(NSLocalizedString("L'accesso alla fotocamera può essere concesso nelle impostazioni di sistema di questa applicazione.", comment: "")), + primaryButton: Alert.Button.default(Text(NSLocalizedString("Impostazioni", comment: ""))) { + guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } + UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil) + }, + secondaryButton: Alert.Button.cancel() + ) + } + } + } + } + + public extension SingleMediaPickerButton where DefaultImageContent == EmptyView { + init( + selectedImage: Binding, + selectedVideo: Binding, + noCameraAccessStrategy: NoPermissionsStrategy = NoPermissionsStrategy.showToSettings, + label: @escaping () -> Content, + onDelete: (() -> Void)? = nil, + allowsEditing: Bool = false + ) { + self.init( + selectedImage: selectedImage, + selectedVideo: selectedVideo, + noCameraAccessStrategy: noCameraAccessStrategy, + label: label, + onDelete: onDelete, + defaultImageContent: nil, + allowsEditing: allowsEditing + ) + } + } +#endif From f7a09f6f49118ae5d210d7f9e7bfb654a3841a81 Mon Sep 17 00:00:00 2001 From: Gabriele Marcato Date: Thu, 17 Nov 2022 12:29:17 +0100 Subject: [PATCH 2/2] feat: user can add custom strings --- .../MediasPickerButton.swift | 32 ++++++++---- .../SingleMediaPickerButton.swift | 52 ++++++++++++------- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/Sources/ImagePickerModule/MediasPickerButton.swift b/Sources/ImagePickerModule/MediasPickerButton.swift index 8634977..01a974b 100644 --- a/Sources/ImagePickerModule/MediasPickerButton.swift +++ b/Sources/ImagePickerModule/MediasPickerButton.swift @@ -22,6 +22,11 @@ import SwiftUI @State private var showCameraMediaPicker: Bool = false @State private var showLibraryMediaPicker: Bool = false + var selectPhotoOrVideoText: String + var takePhotoOrVideoText: String + var grantCameraAndMicrophoneText: String + var grantCameraAndMicrophoneTextMessage: String + var settingsText: String @State private var showMicrophoneAndCameraAccessRequiredAlert: Bool = false public init( @@ -29,13 +34,23 @@ import SwiftUI selectedVideos: Binding<[URL]>, noCameraAccessStrategy: NoPermissionsStrategy = NoPermissionsStrategy.showToSettings, @ViewBuilder label: @escaping () -> Content, - allowsEditing: Bool = false + allowsEditing: Bool = false, + selectPhotoOrVideoText: String = "Selezionare le foto o i video dalla libreria", + takePhotoOrVideoText: String = "Scattare una foto o registrare un video con la fotocamera", + grantCameraAndMicrophoneText: String = "È necessario l'accesso alla telecamera e al microfono", + grantCameraAndMicrophoneTextMessage: String = "L'accesso alla fotocamera e al microfono può essere concesso nelle impostazioni di sistema di questa applicazione.", + settingsText: String = "Impostazioni" ) { _selectedImages = selectedImages _selectedVideos = selectedVideos self.noCameraAccessStrategy = noCameraAccessStrategy self.label = label() self.allowsEditing = allowsEditing + self.selectPhotoOrVideoText = selectPhotoOrVideoText + self.takePhotoOrVideoText = takePhotoOrVideoText + self.grantCameraAndMicrophoneText = grantCameraAndMicrophoneText + self.grantCameraAndMicrophoneTextMessage = grantCameraAndMicrophoneTextMessage + self.settingsText = settingsText } public var body: some View { @@ -45,8 +60,7 @@ import SwiftUI Button( action: { self.showLibraryMediaPicker = true }, label: { - Label(NSLocalizedString("Selezionare le foto o i video dalla libreria", comment: ""), - systemImage: "folder") + Label(self.selectPhotoOrVideoText, systemImage: "folder") } ) @@ -56,8 +70,7 @@ import SwiftUI Button( action: { self.showCameraMediaPicker = true }, label: { - Label(NSLocalizedString("Scattare una foto o registrare un video con la fotocamera", comment: ""), - systemImage: "camera") + Label(self.takePhotoOrVideoText, systemImage: "camera") } ) @@ -67,8 +80,7 @@ import SwiftUI action: { self.showMicrophoneAndCameraAccessRequiredAlert = true }, label: { - Label(NSLocalizedString("Scattare una foto o registrare un video con la fotocamera", comment: ""), - systemImage: "camera") + Label(self.takePhotoOrVideoText, systemImage: "camera") } ) } @@ -100,9 +112,9 @@ import SwiftUI Text("").alert(isPresented: self.$showMicrophoneAndCameraAccessRequiredAlert) { Alert( - title: Text(NSLocalizedString("È necessario l'accesso alla telecamera e al microfono", comment: "")), - message: Text(NSLocalizedString("L'accesso alla fotocamera e al microfono può essere concesso nelle impostazioni di sistema di questa applicazione.", comment: "")), - primaryButton: Alert.Button.default(Text(NSLocalizedString("Impostazioni", comment: ""))) { + title: Text(self.grantCameraAndMicrophoneText), + message: Text(self.grantCameraAndMicrophoneTextMessage), + primaryButton: Alert.Button.default(Text(self.settingsText)) { guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil) }, diff --git a/Sources/ImagePickerModule/SingleMediaPickerButton.swift b/Sources/ImagePickerModule/SingleMediaPickerButton.swift index ebb0a90..db045a3 100644 --- a/Sources/ImagePickerModule/SingleMediaPickerButton.swift +++ b/Sources/ImagePickerModule/SingleMediaPickerButton.swift @@ -31,6 +31,14 @@ import SwiftUI @State private var showSelectedImageOrVideo: Bool = false @State private var showCameraAndMicrophoneAccessRequiredAlert: Bool = false + var selectPhotoOrVideoText: String + var takePhotoOrVideoText: String + var grantCameraAndMicrophoneText: String + var showCurrentPhotoOrVideoText: String + var deleteCurrentPhotoOrVideoText: String + var grantCameraAndMicrophoneTextMessage: String + var settingsText: String + init( selectedImage: Binding, selectedVideo: Binding, @@ -38,7 +46,14 @@ import SwiftUI label: @escaping () -> Content, onDelete: (() -> Void)? = nil, defaultImageContent: (() -> DefaultImageContent)?, - allowsEditing: Bool = false + allowsEditing: Bool = false, + selectPhotoOrVideoText: String = "Selezionare la foto o il video dalla libreria", + takePhotoOrVideoText: String = "Scattare una foto o video con la fotocamera", + showCurrentPhotoOrVideoText: String = "Mostra l'immagine o video corrente", + deleteCurrentPhotoOrVideoText: String = "Rimuovere l'immagine o video corrente", + grantCameraAndMicrophoneText: String = "È necessario l'accesso alla telecamera e al microfono", + grantCameraAndMicrophoneTextMessage: String = "L'accesso alla fotocamera e al microfono può essere concesso nelle impostazioni di sistema di questa applicazione.", + settingsText: String = "Impostazioni" ) { _selectedImage = selectedImage _selectedVideo = selectedVideo @@ -47,6 +62,13 @@ import SwiftUI self.label = label() self.defaultImageContent = defaultImageContent self.allowsEditing = allowsEditing + self.selectPhotoOrVideoText = selectPhotoOrVideoText + self.takePhotoOrVideoText = takePhotoOrVideoText + self.showCurrentPhotoOrVideoText = showCurrentPhotoOrVideoText + self.deleteCurrentPhotoOrVideoText = deleteCurrentPhotoOrVideoText + self.grantCameraAndMicrophoneText = grantCameraAndMicrophoneText + self.grantCameraAndMicrophoneTextMessage = grantCameraAndMicrophoneTextMessage + self.settingsText = settingsText } public var body: some View { @@ -56,8 +78,7 @@ import SwiftUI Button( action: { self.showLibraryMediaPicker = true }, label: { - Label(NSLocalizedString("Selezionare la foto o video dalla libreria", comment: ""), - systemImage: "folder") + Label(self.selectPhotoOrVideoText, systemImage: "folder") } ) @@ -67,8 +88,7 @@ import SwiftUI Button( action: { self.showCameraMediaPicker = true }, label: { - Label(NSLocalizedString("Scattare una foto o video con la fotocamera", comment: ""), - systemImage: "camera") + Label(self.takePhotoOrVideoText, systemImage: "camera") } ) @@ -79,10 +99,7 @@ import SwiftUI self.showCameraAndMicrophoneAccessRequiredAlert = true }, label: { - Label( - NSLocalizedString("Scattare una foto o video con la fotocamera", comment: ""), - systemImage: "camera" - ) + Label(self.takePhotoOrVideoText, systemImage: "camera") } ) } @@ -92,10 +109,7 @@ import SwiftUI Button( action: { self.showSelectedImageOrVideo = true }, label: { - Label( - NSLocalizedString("Mostra l'immagine o video corrente", comment: ""), - systemImage: "arrow.up.backward.and.arrow.down.forward" - ) + Label(self.showCurrentPhotoOrVideoText, systemImage: "arrow.up.backward.and.arrow.down.forward") } ) } @@ -108,10 +122,8 @@ import SwiftUI self.selectedVideo = nil }, label: { - Label( - NSLocalizedString("Rimuovere l'immagine o video corrente", comment: ""), - systemImage: "xmark" - ) + Label(self.deleteCurrentPhotoOrVideoText, + systemImage: "xmark") } ) } @@ -165,9 +177,9 @@ import SwiftUI Text("").alert(isPresented: self.$showCameraAndMicrophoneAccessRequiredAlert) { Alert( - title: Text(NSLocalizedString("È necessario l'accesso alla telecamera", comment: "")), - message: Text(NSLocalizedString("L'accesso alla fotocamera può essere concesso nelle impostazioni di sistema di questa applicazione.", comment: "")), - primaryButton: Alert.Button.default(Text(NSLocalizedString("Impostazioni", comment: ""))) { + title: Text(self.grantCameraAndMicrophoneText), + message: Text(self.grantCameraAndMicrophoneTextMessage), + primaryButton: Alert.Button.default(Text(self.settingsText)) { guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil) },