Add a different modifier to each Action Sheet button - swiftui

This is probably quite a simple question, but I can't find an answer.
I'm trying to build an ActionSheet with two buttons (as well as a cancel button):
Button "Select from Gallery" opens an imagePicker with sourceType set to .photoLibrary.
Button "Take a new picture" opens an imagePicker with sourceType set to .camera.
I've made the ActionSheet and the imagePicker successfully, but can't work out where to add the modifier to tell which sourceType should be used to each button. I managed to add it outside of the ActionSheet in a sheet() modifier in a normal button like this and everything worked well:
Button(action: {
self.show.toggle()
})
{Text("Take a new picture")}
.sheet(isPresented: self.$show, content: {
ImagePicker(sourceType: .camera, show: self.$show, image: self.$imageTemp)
})
However I can't see where to include this information in the ActionSheet. Many thanks to anyone who can help, I hope this is clear :-)
Here is my code:
struct ContentView: View {
#State private var showingActionSheet = false
#State var imageTemp : Data = (UIImage(systemName: "photo.on.rectangle.angled")?.jpegData(compressionQuality: 1))!
var body: some View {
NavigationView {
Image(uiImage: UIImage(data: imageTemp)!)
.onTapGesture {
self.showingActionSheet = true
}
.actionSheet(isPresented: $showingActionSheet) {
ActionSheet(title: Text("Image selector"), message: Text("Select an image"), buttons: [
.default(Text("Select from Gallery"))
{
self.show.toggle()
},
.default(Text("Take new picture")) {
self.show.toggle()
},
.cancel()
]
)
}
}
}
}
And, just in case, here is the code for my imagePicker, although I think it's probably not necessary.
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Binding var show: Bool
#Binding var image: Data
func makeCoordinator() -> ImagePicker.Coordinator {
let imagePicker = UIImagePickerController()
return ImagePicker.Coordinator(child1: self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = sourceType
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var child : ImagePicker
init(child1: ImagePicker) {
child = child1
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.child.show.toggle()
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
let image = info[.originalImage]as! UIImage
let data = image.jpegData(compressionQuality: 0.45)
self.child.image = data!
self.child.show.toggle()
}
}
}

Your question pretty much boils down to "How can I present multiple sheets?", so this thread might be helpful.
Define a new enum to contain possible sheet types (gallery/take photo)
Declare a #State property to hold the current sheet type. It's optional because when it's nil, there will be no sheet presented.
Set the property to the type that you want
Use sheet(item:onDismiss:content:) instead of sheet(isPresented:onDismiss:content:). isPresented is best for static sheets. item is for when you have multiple sheet types, which is what you want.
enum PhotoSheetType: Identifiable { /// 1.
var id: UUID {
UUID()
}
case gallery
case picture
}
struct ContentView: View {
/// 2.
#State private var showingType: PhotoSheetType?
#State private var showingActionSheet = false
#State var imageTemp : Data = (UIImage(systemName: "photo.on.rectangle.angled")?.jpegData(compressionQuality: 1))!
var body: some View {
NavigationView {
Image(uiImage: UIImage(data: imageTemp)!)
.onTapGesture {
self.showingActionSheet = true
}
.actionSheet(isPresented: $showingActionSheet) {
ActionSheet(
title: Text("Image selector"),
message: Text("Select an image"),
buttons: [
.default(Text("Select from Gallery")) {
showingType = .gallery /// 3.
},
.default(Text("Take new picture")) {
showingType = .picture /// 3.
},
.cancel()
]
)
} /// 4.
.sheet(item: $showingType) { type in
if type == .gallery {
ImagePicker(sourceType: .photoLibrary, showingType: $showingType, image: self.$imageTemp)
} else {
ImagePicker(sourceType: .camera, showingType: $showingType, image: self.$imageTemp)
}
}
}
}
}
You'll also need to modify your ImagePicker so that the Binding takes in a PhotoSheetType? instead of Bool. To dismiss the sheet, just set showingType to nil.
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Binding var showingType: PhotoSheetType?
#Binding var image: Data
func makeCoordinator() -> ImagePicker.Coordinator {
let imagePicker = UIImagePickerController()
return ImagePicker.Coordinator(child1: self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = sourceType
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var child : ImagePicker
init(child1: ImagePicker) {
child = child1
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.child.showingType = nil /// set to nil here
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
let image = info[.originalImage] as! UIImage
let data = image.jpegData(compressionQuality: 0.45)
self.child.image = data!
self.child.showingType = nil /// set to nil here
}
}
}

Related

How to select multiple images and print the paths of the images after a button is pressed in swiftui

Good day,
I am trying to make an image picker that you can choose multiple images and then print out the paths of the images once a button has been pressed on the screen.
But I could only manage to get it to pick on image at a time and it prints out the path when the image it picked.
Here is my code for the image picker
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
var selectedImages = [UIImage]()
for item in info {
if let image = item.value as? UIImage, let imageURL = info[.imageURL] as? URL {
selectedImages.append(image)
print("Image URL: ", imageURL)
}
}
self.parent.images.append(contentsOf: selectedImages)
parent.showImagePicker = false
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
#Binding var showImagePicker: Bool
#Binding var images: [UIImage]
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = .photoLibrary
picker.allowsEditing = false
picker.modalPresentationStyle = .fullScreen
picker.mediaTypes = ["public.image"]
picker.videoQuality = .typeHigh
picker.videoMaximumDuration = TimeInterval(30)
picker.modalPresentationStyle = .popover
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
uiViewController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 0, height: 0))
}
}
struct ImagePickerView: View {
#State private var showImagePicker = false
#State private var images = [UIImage]()
var body: some View {
VStack{
Button("Select Images") {
self.showImagePicker = true
}
.sheet(isPresented: $showImagePicker) {
ImagePicker(showImagePicker: $showImagePicker, images: $images)
}
}
}
struct ImagePickerViewPreviews: PreviewProvider {
static var previews: some View {
ImagePickerView()
}
}
}

Issue with Image picker in swiftui for ios 16

Good day,
I am new to ios development and trying to make an image picker to print out the path of the images to eventually store in a json file but I keep getting this error
Cannot convert value of type 'Binding' to expected argument type 'Binding'
Here is the code that I am busy with.
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let images = info[.originalImage] as? [UIImage] {
for image in images {
print(image)
}
}
parent.$presentationMode.wrappedValue.dismiss()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
#Binding var presentationMode: PresentationMode
#Binding var images: [UIImage]
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = .photoLibrary
picker.allowsEditing = false
picker.modalPresentationStyle = .fullScreen
picker.mediaTypes = ["public.image"]
picker.videoQuality = .typeHigh
picker.videoMaximumDuration = TimeInterval(30)
picker.modalPresentationStyle = .popover
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
uiViewController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 0, height: 0))
}
}
struct ImagePickerView: View {
#State private var showImagePicker = false
#State private var images = [UIImage]()
var body: some View {
Button("Select Images") {
self.showImagePicker = true
}
.sheet(isPresented: $showImagePicker, onDismiss: {
self.showImagePicker = false
}, content: {
ImagePicker(presentationMode: self.$showImagePicker, images: self.$images)
})
}
}
struct ImagePickerViewPreviews: PreviewProvider {
static var previews: some View {
ImagePickerView()
}
}
There are a couple of issues:
You're trying to pass a Binding<Bool> to a Binding<PresentationMode>
You are trying to convert to a [UIImage] instead of UIImage
You actually don't need the PresentationMode at all -- you can just use the single Bool binding for showImagePicker since all you're doing is trying to effect whether the view is shown or not.
See comments for the changed lines:
struct ImagePicker: UIViewControllerRepresentable {
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
// <-- Here
if let image = info[.originalImage] as? UIImage {
self.parent.images = [image]
}
parent.showImagePicker = false // <-- Here
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
#Binding var showImagePicker: Bool // <-- Here
#Binding var images: [UIImage]
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = .photoLibrary
picker.allowsEditing = false
picker.modalPresentationStyle = .fullScreen
picker.mediaTypes = ["public.image"]
picker.videoQuality = .typeHigh
picker.videoMaximumDuration = TimeInterval(30)
picker.modalPresentationStyle = .popover
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
uiViewController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 0, height: 0))
}
}
struct ImagePickerView: View {
#State private var showImagePicker = false
#State private var images = [UIImage]()
var body: some View {
Button("Select Images") {
self.showImagePicker = true
}
// <-- Here
.sheet(isPresented: $showImagePicker) {
ImagePicker(showImagePicker: $showImagePicker, images: $images)
}
.onChange(of: images) { newValue in
print("Images: ", newValue)
}
}
}

SwiftUI UIIMagePickerController Find/Search Field Strange Behaviour

I'm using a UIImagePickerController to select an image from the user's photo library. All works fine except when the user taps on the field with the magnifying glass icon to search the library. Sometimes it dismisses ImagePicker, other times it does nothing and occasionally it works as expected... any ideas?
Code to display the sheet:
Button(action: {}) {
ZStack {
Image(systemName: "photo")
.foregroundColor(darkMode ? .white : .black)
}
}
.frame(width: 44, height: 44)
.onTapGesture {
showPhotoLibrary.toggle()
}
.sheet(isPresented: $showPhotoLibrary) {
ImagePicker(sourceType: .photoLibrary, selectedImage: self.$image)
}
The ImagePicker:
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Binding var selectedImage: UIImage
#Environment(\.presentationMode) private var presentationMode
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let imagePicker = UIImagePickerController()
imagePicker.sourceType = sourceType
imagePicker.delegate = context.coordinator
return imagePicker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
internal func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
parent.selectedImage = image
}
parent.presentationMode.wrappedValue.dismiss()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
I've seen many struggle with this, it's all over the Apple dev forums so I thought I would have a go at solving it. From my own testing I believe the reason for the problems is UIImagePickerController is designed to be presented using the present method of a parent View Controller, not used as the root view controller of a SwiftUI presented sheet, as Apple (in their sample code) and everyone else seem to be doing. Below is my incomplete test code, which shows a working image picker where the search works and it can also be dragged down to dismiss, so I think this might be the correct solution.
The idea is we use UIViewControllerRepresentable to present our own custom view controller that presents or dismisses the image picker view controller when the present boolean changes.
struct ImagePickerTest: View {
#State var show = false
#State var image1: UIImage?
var body: some View {
VStack {
Button("Hello, World!") {
show.toggle()
}
Text("image: \(image1?.description ?? "" )")
}
.imagePicking(isPresented: $show, selectedImage: $image1)
}
}
extension View {
// this could be refactored into a `ViewModifier`
func imagePicking(isPresented: Binding<Bool>, selectedImage: Binding<UIImage?>) -> some View {
ZStack {
ImagePicking(isPresented: isPresented, selectedImage: selectedImage)
self
}
}
}
class MyViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
var dismissPicker: (() -> Void)?
var selectImage: ((UIImage) -> Void)?
func showPickerIfNecessary() {
if self.presentedViewController != nil {
return
}
let imagePickerController = UIImagePickerController()
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate = self
present(imagePickerController, animated: true)
}
func hidePickerIfNecessary() {
if let vc = self.presentedViewController {
vc.dismiss(animated: true)
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
print("imagePickerControllerDidCancel")
dismissPicker?()
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
print("didFinishPickingMediaWithInfo")
if let image = (info[.editedImage] ?? info[.originalImage]) as? UIImage {
selectImage?(image)
dismissPicker?()
}
}
}
struct ImagePicking: UIViewControllerRepresentable {
#Binding var isPresented: Bool
#Binding var selectedImage: UIImage?
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
uiViewController.dismissPicker = {
isPresented = false
}
uiViewController.selectImage = { image in
selectedImage = image
}
if isPresented {
uiViewController.showPickerIfNecessary()
} else {
uiViewController.hidePickerIfNecessary()
}
}
func makeUIViewController(context: Context) -> some MyViewController {
MyViewController()
}
}
Before I started, I verified the OP's code was failing when typing a search, it crashed with this error:
2022-08-31 22:39:17.808899+0100 Test[66967:5425868] [UI] -[PUPhotoPickerHostViewController viewServiceDidTerminateWithError:] Error Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)" UserInfo={Message=Service Connection Interrupted}

Can't drag down/dismiss UIColorPickerViewController

I am displaying a UIColorPickerViewController as a sheet using the sheet() method, everything works fine but I can't drag down/dismiss the view anymore.
import Foundation
import SwiftUI
struct ColorPickerView: UIViewControllerRepresentable {
private var selectedColor: UIColor!
init(selectedColor: UIColor) {
self.selectedColor = selectedColor
}
func makeUIViewController(context: Context) -> UIColorPickerViewController {
let colorPicker = UIColorPickerViewController()
colorPicker.selectedColor = self.selectedColor
return colorPicker
}
func updateUIViewController(_ uiViewController: UIColorPickerViewController, context: Context) {
// Silent
}
}
.sheet(isPresented: self.$viewManager.showSheet, onDismiss: {
ColorPickerView()
}
Any idea how to make the drag/down dismiss gesture works?
Thanks!
Ran into the same problem when trying to build a color picker similar to above. What worked was "wrapping" the color picker in a view with a Dismiss button. And also discovered that the bar at the top of the view would allow the picker to now be dragged down and away. Below is my wrapper. (One could add more features such as a title to the bar.)
struct ColorWrapper: View {
var inputColor: UIColor
#Binding var isShowingColorPicker: Bool
#Binding var selectedColor: UIColor?
var body: some View {
VStack {
HStack {
Spacer()
Button("Dismiss", action: {
isShowingColorPicker = false
}).padding()
}
ColorPickerView(inputColor: inputColor, selectedColor: $selectedColor)
}
}
}
And for completeness, here is my version of the color picker:
import SwiftUI
struct ColorPickerView: UIViewControllerRepresentable {
typealias UIViewControllerType = UIColorPickerViewController
var inputColor: UIColor
#Binding var selectedColor: UIColor?
#Environment(\.presentationMode) var isPresented
func makeUIViewController(context: Context) -> UIColorPickerViewController {
let picker = UIColorPickerViewController()
picker.delegate = context.coordinator
picker.supportsAlpha = false
picker.selectedColor = inputColor
return picker
}
func updateUIViewController(_ uiViewController: UIColorPickerViewController, context: Context) {
uiViewController.supportsAlpha = false
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIColorPickerViewControllerDelegate {
var parent: ColorPickerView
init(parent: ColorPickerView) {
self.parent = parent
}
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
parent.isPresented.wrappedValue.dismiss()
}
func colorPickerViewController(_ viewController: UIColorPickerViewController, didSelect color: UIColor, continuously: Bool) {
parent.selectedColor = color
// parent.isPresented.wrappedValue.dismiss()
}
}
}

How to change #State property wrapper from nested view

I'm wondering how I should change #State property wrapper showErrorAlert in view below
struct SettingsView: View {
#State private var shouldPresent = false
#State var showErrorAlert = false
var body: some View {
VStack {
Form {
Text("Settings")
.font(.title)
Button("Import source data") {
self.shouldPresent.toggle()
}
.sheet(isPresented: $shouldPresent) {
DocumentPicker()
}
Button("Show error alert") {
self.showErrorAlert.toggle()
}
.alert(isPresented: $showErrorAlert, content: {
Alert(title: Text("Error"))
})
}
}
}
}
from DocumentPicker struct code in case that reading of selected file fails.
struct DocumentPicker: UIViewControllerRepresentable {
func makeCoordinator() -> DocumentPicker.Coordinator {
return DocumentPicker.Coordinator(parent: self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentPicker>) -> UIDocumentPickerViewController {
let picker = UIDocumentPickerViewController(documentTypes: [String(kUTTypeJSON)], in: .import)
picker.allowsMultipleSelection = false
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext<DocumentPicker>) {
}
class Coordinator: NSObject, UIDocumentPickerDelegate {
var myParent: DocumentPicker
init(parent: DocumentPicker) {
myParent = parent
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let fileURL = urls.first!
do {
let origFile = try String(contentsOf: fileURL)
//File processing will be here
} catch let error {
print(error)
}
}
}
}
I mean how to set property wrapper value to true to show the alert. Should I rather use #ObservedObject or #EnvironmentObject instead?
Thanks.
To change the wrapper value in your DocumentPicker struct you can define a #Binding variable and pass your value to it, this toggle your variable on your parent view, but before showing the alert you need to dismiss the DocumentPicker