How to open the ImagePicker in SwiftUI?
Cleaned up version for Xcode 12 available via SPM as Swift Package:
https://github.com/ralfebert/ImagePickerView
Source:
import SwiftUI
public struct ImagePickerView: UIViewControllerRepresentable {
private let sourceType: UIImagePickerController.SourceType
private let onImagePicked: (UIImage) -> Void
@Environment(\.presentationMode) private var presentationMode
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
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[.originalImage] as? UIImage {
self.onImagePicked(image)
}
self.onDismiss()
}
public func imagePickerControllerDidCancel(_: UIImagePickerController) {
self.onDismiss()
}
}
}
ImagePicker in SwiftUI
ok, looks like you are testing with 13.1 -> update to 13.2 - there it works
How to pick image from gallery in SwiftUI
I am using presentationMode
here, to check for the view that is it presenting or not?
By using this you can dismiss()
the UIImagePickerController
.
Sample code for SwiftUI preview:
import SwiftUI
struct ContentView: View {
@State var isShowPicker: Bool = false
@State var image: Image? = Image("placeholder")
var body: some View {
NavigationView {
ZStack {
VStack {
image?
.resizable()
.scaledToFit()
.frame(height: 320)
Button(action: {
withAnimation {
self.isShowPicker.toggle()
}
}) {
Image(systemName: "photo")
.font(.headline)
Text("IMPORT").font(.headline)
}.foregroundColor(.black)
Spacer()
}
}
.sheet(isPresented: $isShowPicker) {
ImagePicker(image: self.$image)
}
.navigationBarTitle("Pick Image")
}
}
}
struct ImagePicker: UIViewControllerRepresentable {
@Environment(\.presentationMode)
var presentationMode
@Binding var image: Image?
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
@Binding var presentationMode: PresentationMode
@Binding var image: Image?
init(presentationMode: Binding<PresentationMode>, image: Binding<Image?>) {
_presentationMode = presentationMode
_image = image
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
image = Image(uiImage: uiImage)
presentationMode.dismiss()
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
presentationMode.dismiss()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(presentationMode: presentationMode, image: $image)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ContentView()
}
}
}
How to get the image from image picker and display into a add form?
You can use a Binding
to pass data from a child view back up to a parent:
struct FileView: View {
@Binding var fileUrl : URL?
var body: some View {
Button("Select File") {
let openPanel = NSOpenPanel()
openPanel.prompt = "Select File"
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = false
openPanel.canCreateDirectories = false
openPanel.canChooseFiles = true
openPanel.allowedFileTypes = ["png","jpg","jpeg"]
openPanel.begin { (result) -> Void in
if result.rawValue == NSApplication.ModalResponse.OK.rawValue {
fileUrl = openPanel.url
}
}
}
}
}
struct ContentView: View {
@State var fileUrl: URL?
var body: some View {
VStack {
FileView(fileUrl: $fileUrl)
if let fileUrl = fileUrl, let image = NSImage(contentsOf: fileUrl) {
Image(nsImage: image)
}
}
}
}
Notice that in the child, it's @Binding
, but in the parent, it's @State
.
Updated version, using Data
:
struct FileView: View {
@Binding var fileData : Data?
var body: some View {
Button("Select File") {
let openPanel = NSOpenPanel()
openPanel.prompt = "Select File"
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = false
openPanel.canCreateDirectories = false
openPanel.canChooseFiles = true
openPanel.allowedFileTypes = ["png","jpg","jpeg"]
openPanel.begin { (result) -> Void in
if result.rawValue == NSApplication.ModalResponse.OK.rawValue {
guard let url = openPanel.url, let data = try? Data(contentsOf: url) else {
//handle errors here
return
}
fileData = data
}
}
}
}
}
struct ContentView: View {
@State var fileData: Data?
var body: some View {
VStack {
FileView(fileData: $fileData)
if let fileData = fileData, let image = NSImage(data: fileData) {
Image(nsImage: image)
}
}
}
}
Note: obviously, not doing any error handling for not being able to read from the URL or anything in this example
SwiftUI MacOSX ImagePicker
initialize the panel like this:
let myFiledialog = NSOpenPanel()
myFiledialog.prompt = “Select path”
to add a selectbox like you want you need to add this:
myFiledialog.worksWhenModal = true
myFiledialog.canChooseDirectories = false
myFiledialog.canChooseFiles = true
you can select the datatype of your choice with
myFiledialog.allowedFileTypes = [“png”, “jpg”, “jpeg”]
to disable the multiselect add this.
myFiledialog.allowsMultipleSelection = false
i hope this helped you.
How do I make two separate calls to an Imagepicker in SwiftUI?
The root cause of the problem is using sheet(isPresented:)
which can be problematic on iOS 14 because it loads the sheet content of the first render of it (in your case, the ImagePicker
for the first image) and then doesn't update as one might expect (see SwiftUI @State and .sheet() ios13 vs ios14 for example).
The solution is to use sheet(item:)
. In your case, this also involved some other refactoring to get things to work as expected. Here's what I came up with:
struct ContentView: View {
@State private var inputImage1: UIImage?
@State private var inputImage2: UIImage?
@State private var activeImagePicker : ImagePickerIdentifier? = nil
enum ImagePickerIdentifier : String, Identifiable {
case picker1, picker2
var id : String {
return self.rawValue
}
}
var image1 : Image {
if let inputImage1 = inputImage1 {
return Image(uiImage: inputImage1)
} else {
return Image("Placeholder")
}
}
var image2 : Image {
if let inputImage2 = inputImage2 {
return Image(uiImage: inputImage2)
} else {
return Image("Placeholder")
}
}
var body: some View {
VStack{
Form {
Section(header: Text("First Image")){
image1
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipShape(Rectangle())
.onTapGesture { self.activeImagePicker = .picker1 }
}
Section(header: Text("Second Image")) {
image2
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 200, alignment: .center)
.clipShape(Rectangle())
.onTapGesture { self.activeImagePicker = .picker2 }
}
}
.sheet(item: $activeImagePicker) { picker in
switch picker {
case .picker1:
ImagePicker(image: $inputImage1)
case .picker2:
ImagePicker(image: $inputImage2)
}
}
}
}
}
Note that I got rid of your onDismiss
function, because it was unnecessary here, but if you did need it for your real implementation, I'd suggest using onDisappear
on the individual ImagePicker
s, which will let you differentiate which one is being dismissed.
Related Topics
Nsinternalinconsistencyexception, Reason: Could Not Load Nib in Bundle
Conditionally Import a Framework (Such as Speech) Based on iOS Version in Swift
How to Edit Uialertaction Text Font Size and Color
How to Set Mime Type of Application/Vnd.Apple.Pkpass in Order to Share Pass by Link or Email
Google Places Autocomplete on iOS - Can't Load Search Results - Try Again
iOS Automatically Add Hyphen in Text Field
How to Call Method from Viewcontroller in Gamescene
Find Attributes from Attributed String That User Typed
How to Tap on a Specific Point Using Xcode Uitests
Xcode 7 Supporting Watch Os1 and Os2
Uiwebview and Safari Comparison
Changed +Load Method Order in Xcode 7
How to Calculate Actual Font Point Size in iOS 7 (Not the Bounding Rectangle)
Setting "Applelanguages" Doesn't Change App Language
Singleton in iOS Objective C Doesn't Prevent More Than One Instance