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..01a974b
--- /dev/null
+++ b/Sources/ImagePickerModule/MediasPickerButton.swift
@@ -0,0 +1,127 @@
+//
+// 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
+ var selectPhotoOrVideoText: String
+ var takePhotoOrVideoText: String
+ var grantCameraAndMicrophoneText: String
+ var grantCameraAndMicrophoneTextMessage: String
+ var settingsText: String
+ @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,
+ 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 {
+ ZStack {
+ Menu(
+ content: {
+ Button(
+ action: { self.showLibraryMediaPicker = true },
+ label: {
+ Label(self.selectPhotoOrVideoText, systemImage: "folder")
+ }
+ )
+
+ if AVCaptureDevice.authorizationStatus(for: .video) == .authorized ||
+ AVCaptureDevice.authorizationStatus(for: .video) == .notDetermined
+ {
+ Button(
+ action: { self.showCameraMediaPicker = true },
+ label: {
+ Label(self.takePhotoOrVideoText, systemImage: "camera")
+ }
+ )
+
+ } else if AVCaptureDevice.authorizationStatus(for: .video) == .denied {
+ if self.noCameraAccessStrategy == NoPermissionsStrategy.showToSettings {
+ Button(
+ action: { self.showMicrophoneAndCameraAccessRequiredAlert = true
+ },
+ label: {
+ Label(self.takePhotoOrVideoText, 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(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)
+ },
+ secondaryButton: Alert.Button.cancel()
+ )
+ }
+ }
+ }
+ }
+#endif
diff --git a/Sources/ImagePickerModule/SingleMediaPickerButton.swift b/Sources/ImagePickerModule/SingleMediaPickerButton.swift
new file mode 100644
index 0000000..db045a3
--- /dev/null
+++ b/Sources/ImagePickerModule/SingleMediaPickerButton.swift
@@ -0,0 +1,213 @@
+//
+// 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
+
+ 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,
+ noCameraAccessStrategy: NoPermissionsStrategy = NoPermissionsStrategy.showToSettings,
+ label: @escaping () -> Content,
+ onDelete: (() -> Void)? = nil,
+ defaultImageContent: (() -> DefaultImageContent)?,
+ 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
+ self.noCameraAccessStrategy = noCameraAccessStrategy
+ self.onDelete = onDelete
+ 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 {
+ ZStack {
+ Menu(
+ content: {
+ Button(
+ action: { self.showLibraryMediaPicker = true },
+ label: {
+ Label(self.selectPhotoOrVideoText, systemImage: "folder")
+ }
+ )
+
+ if AVCaptureDevice.authorizationStatus(for: .video) == .authorized ||
+ AVCaptureDevice.authorizationStatus(for: .video) == .notDetermined
+ {
+ Button(
+ action: { self.showCameraMediaPicker = true },
+ label: {
+ Label(self.takePhotoOrVideoText, systemImage: "camera")
+ }
+ )
+
+ } else if AVCaptureDevice.authorizationStatus(for: .video) == .denied {
+ if self.noCameraAccessStrategy == NoPermissionsStrategy.showToSettings {
+ Button(
+ action: {
+ self.showCameraAndMicrophoneAccessRequiredAlert = true
+ },
+ label: {
+ Label(self.takePhotoOrVideoText, systemImage: "camera")
+ }
+ )
+ }
+ }
+
+ if self.defaultImageContent != nil || self.selectedImage != nil || self.selectedVideo != nil {
+ Button(
+ action: { self.showSelectedImageOrVideo = true },
+ label: {
+ Label(self.showCurrentPhotoOrVideoText, systemImage: "arrow.up.backward.and.arrow.down.forward")
+ }
+ )
+ }
+
+ if let onDelete = self.onDelete {
+ Button(
+ action: {
+ onDelete()
+ self.selectedImage = nil
+ self.selectedVideo = nil
+ },
+ label: {
+ Label(self.deleteCurrentPhotoOrVideoText,
+ 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(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)
+ },
+ 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