Pick and play a video in SwiftUI — how convert from UIKit code? - swift

I'm working on a camera app and I've got a problem.
I've never used UIKit to build an app, but a lot of the reference code does.
So I tried to convert it using swiftUI but I failed.
There is UIKit code which I want to convert to SwiftUI.
static func startMediaBrowser(
delegate: UIViewController & UINavigationControllerDelegate & UIImagePickerControllerDelegate,
sourceType: UIImagePickerController.SourceType
) {
guard UIImagePickerController.isSourceTypeAvailable(sourceType)
else { return }
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType
mediaUI.mediaTypes = [kUTTypeMovie as String]
mediaUI.allowsEditing = true
mediaUI.delegate = delegate
delegate.present(mediaUI, animated: true, completion: nil)
}
import AVKit
import MobileCoreServices
import UIKit
class PlayVideoViewController: UIViewController {
#IBAction func playVideo(_ sender: AnyObject) {
VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
}
}
// MARK: - UIImagePickerControllerDelegate
extension PlayVideoViewController: UIImagePickerControllerDelegate {
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
else { return }
dismiss(animated: true) {
let player = AVPlayer(url: url)
let vcPlayer = AVPlayerViewController()
vcPlayer.player = player
self.present(vcPlayer, animated: true, completion: nil)
}
}
}
// MARK: - UINavigationControllerDelegate
extension PlayVideoViewController: UINavigationControllerDelegate {
}
Here's what I've tried, and the compilation passes, but it only does UIImagePickerController() , and the delegate function I wrote doesn't work.
import SwiftUI
import AVKit
import MobileCoreServices
import UIKit
struct ContentView: View {
#State private var isShowVideoLibrary = false
#State private var image = UIImage()
#State private var isShowCamara = false
var body: some View {
VStack {
HStack{
Button {
isShowVideoLibrary.toggle()
} label: {
Text("Play video")
}
}
}
.sheet(isPresented: $isShowVideoLibrary) {
VideoPicker(sourceType: .photoLibrary)
}
}
struct VideoPicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Environment(\.presentationMode) private var presentationMode
func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPicker>) -> UIViewController {
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType
mediaUI.mediaTypes = [kUTTypeMovie as String]
mediaUI.allowsEditing = true
mediaUI.delegate = context.coordinator
return mediaUI
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
final class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
var parent: VideoPicker
init(_ parent: VideoPicker) {
self.parent = parent
}
private func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) -> UIViewController {
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
else { return AVPlayerViewController()}
// 2
parent.presentationMode.wrappedValue.dismiss()
//3
let player = AVPlayer(url: url)
let vcPlayer = AVPlayerViewController()
vcPlayer.player = player
return vcPlayer
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}

The problem you have is that you haven't implemented the correct UIImagePickerControllerDelegate function signature.
Your Coordinator has:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) -> UIViewController
while the correct method is:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
The method won't get called unless the signature matches exactly.
A solution
The UIImagePickerController is just used to select the image or video, you'll need additional cod to play the selected video. Luckily SwiftUI has a VideoPlayer that makes it easy:
import UniformTypeIdentifiers
struct ContentView: View {
#State private var isShowVideoLibrary = false
#State private var url: URL?
var body: some View {
Group {
if let url {
VideoPlayer(player: AVPlayer(url: url))
} else {
VStack {
HStack{
Button {
isShowVideoLibrary.toggle()
} label: {
Text("Play video")
}
}
}
}
}
.sheet(isPresented: $isShowVideoLibrary) {
VideoPicker(sourceType: .photoLibrary) { url in
self.url = url
isShowVideoLibrary = false
}
}
}
}
struct VideoPicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
let didFinish: (URL?) -> Void
func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPicker>) -> UIViewController {
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType
mediaUI.mediaTypes = [UTType.movie.identifier]
mediaUI.allowsEditing = true
mediaUI.delegate = context.coordinator
return mediaUI
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
final class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
let didFinish: (URL?) -> Void
init(didFinish: #escaping (URL?) -> Void) {
self.didFinish = didFinish
}
// This func passes the URL back to the calling View
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == UTType.movie.identifier,
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
else {
didFinish(nil)
return
}
didFinish(url)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(didFinish: didFinish)
}
}

Related

Is there a way to make PhotosPicker in SwiftUI use the camera instead?

Is there a way to make the PhotosUI PhotosPicker in SwiftUI source the photos directly from the Camera? I'm trying to change the PhotosPickers in my app to use the camera instead of forcing the user to select images from their image library. Is there a way to dictate the source of PhotosPicker images like you can with ImagePicker?
I did an online search and a lot of articles talked about PhotoPicker, but none of the examples had a way to make it use the camera. Should I just bite the bullet and switch everything to image picker or is there a way to make PhotoPicker use the camera?
Thanks.
step 1 : Create a dialogue to select the option for a camera of the photo
step 2 : Create a action sheet for for image picker
struct BaseView: View {
#State var showSelection: Bool = false
#State var showPicker: Bool = false
#State var type: UIImagePickerController.SourceType = .photoLibrary
var body: some View {
Zstack {
YourView()
}
.confirmationDialog(Lables.selectImage,
isPresented: $showSelection,
titleVisibility: .hidden) {
Button(ButtonName.camera) {
showPicker = true
type = .camera
}
Button(ButtonName.photos) {
showPicker = true
type = .photoLibrary
}
}
.fullScreenCover(isPresented: $showPicker) {
ImagePickerView(sourceType: profileImageVM.pickerType) { image in
// image your image
}
}
}
}
step 3: Create Image Picker
struct ImagePickerView: UIViewControllerRepresentable {
private var 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()
}
}
}

QLPreviewController showing file then going blank in SwiftUI

I've added a UIViewControllerRepresentable for UIKit's QLPreviewController which I've found in a related question:
struct QuickLookView: UIViewControllerRepresentable {
var url: URL
var onDismiss: (() -> Void) = { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func updateUIViewController(_ viewController: UINavigationController, context: UIViewControllerRepresentableContext<Self>) {
(viewController.topViewController as? QLPreviewController)?.reloadData()
}
func makeUIViewController(context: Context) -> UINavigationController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
controller.reloadData()
return UINavigationController(rootViewController: controller)
}
class Coordinator: NSObject, QLPreviewControllerDataSource {
var parent: QuickLookView
init(_ qlPreviewController: QuickLookView) {
self.parent = qlPreviewController
super.init()
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
self.parent.url as QLPreviewItem
}
}
}
In my app, I download a file (jpg/png/pdf) via Alamofire:
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.documentsDirectory
.appendingPathComponent(document.id.string)
.appendingPathComponent(document.name ?? "file.jpg")
return (documentsURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF
.download(url, to: destination)
.responseURL { (response) in
guard let url = response.fileURL else { return }
self.fileURL = url
self.isShowingDoc = true
}
...and pass its local url to the QuickLookView to present it:
#State private var isShowingDoc = false
#State private var fileURL: URL?
var body: some View {
// ...
.sheet(isPresented: $isShowingDoc, onDismiss: { isShowingDoc = false }) {
QuickLookView(url: fileURL!) {
isShowingDoc = false
}
}
}
What happens is that the QuickLookView opens as sheet, the file flashes (is displayed for like 0.1 seconds) and then the view goes blank:
I checked the Documents folder of the app in Finder and the file is there and matches the url passed to the QuickLookView. I've noticed that when the view is open, and I then delete the file from the folder via Finder, then the view will throw an error saying there's no such file – that means it did read it properly before it was deleted.
Note: I read somewhere that the QL controller has had issues when placed inside a navigation controller. In my view hierarchy, my views are embedded inside a NavigationView – might that cause issues?
How do I solve this?
You just need to update the view before presenting the sheet otherwise it wont work. It can be the button title, opacity or anything. Although it looks like a hack it works fine. I will be very glad if someone explains why it happens and if there is a proper way to make it work without updating the view.
import SwiftUI
struct ContentView: View {
#State private var fileURL: URL!
#State private var isDisabled = false
#State private var isDownloadFinished = false
#State private var buttonTitle: String = "Download PDF"
private let url = URL(string: "https://www.dropbox.com/s/bxrhk6194lf0n73/macpro_mid2010-macpro_mid2012.pdf?dl=1")!
var body: some View {
Button(buttonTitle) {
isDisabled = true
buttonTitle = "Downloading..."
URLSession.shared.downloadTask(with: url) { location, response, error in
guard
let location = location, error == nil,
let suggestedFilename = (response as? HTTPURLResponse)?.suggestedFilename,
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
else { return }
fileURL = documentDirectory.appendingPathComponent(suggestedFilename)
if !FileManager.default.fileExists(atPath: fileURL.path) {
do {
try FileManager.default.moveItem(at: location, to: fileURL)
} catch {
print(error)
}
}
DispatchQueue.main.async {
isDownloadFinished = true
buttonTitle = "" // you need to change the view prefore presenting the sheet otherwise it wont work
}
}.resume()
}
.disabled(isDisabled == true)
.sheet(isPresented: $isDownloadFinished) {
isDisabled = false
isDownloadFinished = false
fileURL = nil
buttonTitle = "Download PDF"
} content: {
if isDownloadFinished {
PreviewController(previewItems: [PreviewItem(url: fileURL, title: fileURL?.lastPathComponent)], index: 0)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
import QuickLook
struct PreviewController: UIViewControllerRepresentable {
var previewItems: [PreviewItem] = []
var index: Int
func makeCoordinator() -> Coordinator { .init(self) }
func updateUIViewController(_ viewController: UINavigationController, context: UIViewControllerRepresentableContext<Self>) {
(viewController.topViewController as? QLPreviewController)?.reloadData()
}
func makeUIViewController(context: Context) -> UINavigationController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
controller.delegate = context.coordinator
controller.reloadData()
return .init(rootViewController: controller)
}
class Coordinator: NSObject, QLPreviewControllerDataSource, QLPreviewControllerDelegate {
let previewController: PreviewController
init(_ previewController: PreviewController) {
self.previewController = previewController
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
previewController.previewItems.count
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
previewController.previewItems[index]
}
}
}
class PreviewItem: NSObject, QLPreviewItem {
var previewItemURL: URL?
var previewItemTitle: String?
init(url: URL? = nil, title: String? = nil) {
previewItemURL = url
previewItemTitle = title
}
}
I finally got it to work – big thanks to Leo Dabus for his help in the comments.
Here's my currently working code:
#State private var isShowingDoc = false
#State private var isLoadingFile = false
#State private var fileURL: URL?
var body: some View {
Button {
let destination: DownloadRequest.Destination = { _, _ in
let documentsURL = FileManager.default.documentsDirectory
.appendingPathComponent(document.id.string)
.appendingPathComponent(document.name ?? "file.jpg")
return (documentsURL, [.removePreviousFile, .createIntermediateDirectories])
}
isLoadingFile = true
AF
.download(url, to: destination)
.responseURL { (response) in
self.isLoadingFile = false
guard let url = response.fileURL else { return }
isShowingDoc = true
self.fileURL = url
}
} label: {
VStack {
Text("download")
if isLoadingFile {
ActivityIndicator(style: .medium)
}
}
}
.sheet(isPresented: $isShowingDoc, onDismiss: { isShowingDoc = false }) {
QuickLookView(url: fileURL!)
}
}
with this QuickLookView: (mostly unchanged)
struct QuickLookView: UIViewControllerRepresentable {
var url: URL
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func updateUIViewController(_ viewController: UINavigationController, context: UIViewControllerRepresentableContext<Self>) {
(viewController.topViewController as? QLPreviewController)?.reloadData()
}
func makeUIViewController(context: Context) -> UINavigationController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
controller.reloadData()
return UINavigationController(rootViewController: controller)
}
class Coordinator: NSObject, QLPreviewControllerDataSource {
var parent: QuickLookView
init(_ qlPreviewController: QuickLookView) {
self.parent = qlPreviewController
super.init()
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
self.parent.url as QLPreviewItem
}
}
}
As you can see, there's hardly any difference to my code from when I asked the question. Yesterday night, the fileURL was always nil for an unclear reason; yet, now it started working just fine. In exchange, the remote images in my list (not shown here) stopped working even though I haven't touched them, haha.
I don't know what's going on and what I even changed to make it work, but it works and I won't complain!

having a memory leak when implementing a image picker in SwiftUI using UIViewControllerRepresentable

I am trying to get the user to pick an image from their gallery. After doing some research I was able to accomplish this task by using a UIViewControllerRepresentable. However, after I opened up the memory debugger it shows that I have a memory leak
I created a basic example to which would make the code a lot simpler to read and still address the same issue without copy-pasting my entire code. the code below also shows a memory leak
// code for the views
import SwiftUI
import UIKit
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: ImagePickerView(),
label: {
Text("Navigate")
})
}
}
}
struct ImagePickerView: View {
#State var image = UIImage()
#State var showController: Bool = false
#State var didChoose : Bool = false
var body: some View {
Button(action: {
showController = true
}, label: {
Text("pick image")
})
.sheet(isPresented: $showController, content: {
ImagePicker(image: $image, didChoose: $didChoose)
})
}
}
struct ImagePicker : UIViewControllerRepresentable {
#Binding var image: UIImage
#Binding var didChoose: Bool
#Environment(\.presentationMode) var presentation
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIViewController {
let controller = UIImagePickerController()
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: ImagePicker.UIViewControllerType, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent : ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let pickedImage = info[.originalImage] as? UIImage else {
print("could not unwrap the image")
return
}
print(pickedImage)
self.parent.image = pickedImage
self.parent.didChoose = true
self.parent.presentation.wrappedValue.dismiss()
}
}
}
I suspect the cause to be the in the coordinator class however, ifailed to identify the reason behind it. is it involved with the parent?

ProgressView Not Updating

I am trying to observe the progress when uploading an image to firebase storage. The progress is being updated in the console however my ProgressView does not update. Everything else seems to be working as expected. Any idea what I'm doing wrong?
class UserManager: ObservableObject {
#Published var taskProgress: Float = 0.0
let user = Auth.auth().currentUser
func uploadProfilePicture(image: UIImage) {
let uploadRef = FirebaseReferenceManager.storage.reference(withPath: "profile/\(user!.uid)")
guard let imageData = image.jpegData(compressionQuality: 0.75) else {
return
}
let uploadMetaData = StorageMetadata.init()
uploadMetaData.contentType = "image/jpeg"
let taskReference = uploadRef.putData(imageData, metadata: uploadMetaData) { (downloadMetaData, error) in
if let error = error {
print(error.localizedDescription)
return
} else {
print("Successfully Uploaded Profile Picture to Firebase Storage")
let downloadRef = FirebaseReferenceManager.storage.reference(withPath: "profile/\(self.user!.uid)")
downloadRef.downloadURL { (url, error) in
if let error = error {
print(error.localizedDescription)
return
}
FirebaseReferenceManager.root.collection(FirebaseKeys.CollectionPath.users).document(self.user!.uid).setData([FirebaseKeys.UsersFieldPath.photoURL : url!.absoluteString], merge: true)
}
}
}
taskReference.observe(.progress) { [weak self] (snapshot) in
DispatchQueue.main.async {
guard let pctThere = snapshot.progress?.fractionCompleted else {return}
print(pctThere)
self?.taskProgress = Float(pctThere)
}
}
taskReference.resume()
}
}
In my view I put the following
#EnvironmentObject var userManager: UserManager
ProgressView(value: userManager.taskProgress).progressViewStyle(LinearProgressViewStyle())
Here is how the class is initialized:
struct SwiftUIView: View {
#StateObject var userManager = UserManager()
var body: some View {
TabView {
HomeView().tabItem {
Image("home")
Text("Home")
}
SearchView().tabItem {
Image("search")
Text("Search")
}
DiscoverView().tabItem {
Image("discover")
Text("Discover")
}
OrdersView().tabItem {
Image("calendar")
Text("Orders")
}
InboxView().tabItem {
Image("inbox")
Text("Inbox")
}
}
.environmentObject(userManager)
}
}
And then I pass it down deeper into the hierarchy using
#EnvironmentObject var userManager: UserManager
Here is where uploadProfilePicture() is called:
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.delegate = context.coordinator
imagePicker.allowsEditing = true
imagePicker.sourceType = sourceType
return imagePicker
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: ImagePicker
#ObservedObject private var userManager = UserManager()
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.editedImage] as? UIImage {
parent.selectedImage = image
userManager.uploadProfilePicture(image: image)
}
parent.presentationMode.wrappedValue.dismiss()
}
}
}
From the comments, we deduced that it was a different UserManager instance. Here would be an example of how to pass the same instance into the Coordinator (this is assuming that ImagePicker exists in an environment where #EnvironmentObject var userManager: UserManager is available).
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Binding var selectedImage: UIImage
#Environment(\.presentationMode) private var presentationMode
#EnvironmentObject private var userManager: UserManager //<-- Here
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let imagePicker = UIImagePickerController()
imagePicker.delegate = context.coordinator
imagePicker.allowsEditing = true
imagePicker.sourceType = sourceType
return imagePicker
}
func makeCoordinator() -> Coordinator {
Coordinator(self, userManager: userManager) //<-- Here
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: ImagePicker
let userManager: UserManager
init(_ parent: ImagePicker, userManager: UserManager) { //<-- Here
self.parent = parent
self.userManager = userManager //<-- Here
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.editedImage] as? UIImage {
parent.selectedImage = image
userManager.uploadProfilePicture(image: image)
}
parent.presentationMode.wrappedValue.dismiss()
}
}
}

Opening Finder using Mac Catalyst

I'm trying to open the finder using Mac Catalyst and get images.
At first, I tried the below code, but Xcode says 'NSOpenPanel' is unavailable in Mac Catalyst .
private func selectFile() {
NSOpenPanel.openImage { (result) in
if case let .success(image) = result {
self.image = image
}
}
}
So I tried this solution, this time Compile was successful, but when I click the button to open finder I got this error message: Thread 1: EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
My Final Code is below
import SwiftUI
struct ContentView: View {
var body: some View {
VStack{
Button("Choose file") {
let picker = DocumentPickerViewController(
supportedTypes: ["log"],
onPick: { url in
print("url : \(url)")
},
onDismiss: {
print("dismiss")
}
)
UIApplication.shared.windows.first?.rootViewController?.present(picker, animated: true)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class DocumentPickerViewController: UIDocumentPickerViewController {
private let onDismiss: () -> Void
private let onPick: (URL) -> ()
init(supportedTypes: [String], onPick: #escaping (URL) -> Void, onDismiss: #escaping () -> Void) {
self.onDismiss = onDismiss
self.onPick = onPick
super.init(documentTypes: supportedTypes, in: .open)
allowsMultipleSelection = false
delegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension DocumentPickerViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
onPick(urls.first!)
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
onDismiss()
}
}
How can I solve this issue?
In my macCatalyst app, I use a UIViewControllerRepresentable to show a UIDocumentPickerViewController
#Binding to your main view so you can reference that data from the selected file
struct DocumentImportViewController: UIViewControllerRepresentable {
#Binding var imgData:Data! //This doesn't need to be a "Data"
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentImportViewController>) -> UIDocumentPickerViewController {
let vc = UIDocumentPickerViewController(forOpeningContentTypes: [.image], asCopy: true)
vc.delegate = context.coordinator
return vc
}
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext<DocumentImportViewController>) {
}
}
Coordinator for delegates
extension DocumentImportViewController{
class Coordinator: NSObject, UIDocumentPickerDelegate {
var parent: DocumentImportViewController
init(_ parent: DocumentImportViewController) {
self.parent = parent
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
parent.imgData = //get image from url
}
}}