swiftuiimagepickerswiftui-actionsheet

Add a different modifier to each Action Sheet button


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):

  1. Button "Select from Gallery" opens an imagePicker with sourceType set to .photoLibrary.
  2. 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()
    }
}
}

Solution

  • Your question pretty much boils down to "How can I present multiple sheets?", so this thread might be helpful.

    1. Define a new enum to contain possible sheet types (gallery/take photo)
    2. Declare a @State property to hold the current sheet type. It's optional because when it's nil, there will be no sheet presented.
    3. Set the property to the type that you want
    4. 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
            }
        }
    }