ProgressView Not Updating - swift

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()
}
}
}

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()
}
}
}

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

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

SwiftUI: Is it possible to let the user scale an image chosen with PHpicker?

I have an image picker created with PHPicker, and I was wondering if it is possible to let the user scale the chosen image?
This is not the entire code, but just the code for the makeUIViewController which I think is what is needed to solve this problem. I can of course provide the rest of the code if necessary.
This is what I'm looking for
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
config.filter = .images
config.selectionLimit = 1
let controller = PHPickerViewController(configuration: config)
controller.delegate = context.coordinator
return controller
}
can use this one line after choose the image to fixed height and width of your image
Image(room.thumbnailImage)
.resizable()
.frame(width: 32.0, height: 32.0)
or here i am sharing my running work with you checkout function didFinishPicking and var body: some View
import SwiftUI
import PhotosUI
struct PhotoPickerDemo: View {
#State private var isPresented: Bool = false
#State var pickerResult: [UIImage] = []
var config: PHPickerConfiguration {
var config = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
config.filter = .images //videos, livePhotos...
config.selectionLimit = 0 //0 => any, set 1-2-3 for har limit
return config
}
var body: some View {
ScrollView {
LazyVStack {
Button("Present Picker") {
isPresented.toggle()
}.sheet(isPresented: $isPresented) {
PhotoPicker(configuration: self.config,
pickerResult: $pickerResult,
isPresented: $isPresented)
}
ForEach(pickerResult, id: \.self) { image in
Image.init(uiImage: image)
.resizable()
.frame(width: UIScreen.main.bounds.width, height: 250, alignment: .center)
.aspectRatio(contentMode: .fit)
}
}
}
}
}
struct PhotoPicker: UIViewControllerRepresentable {
let configuration: PHPickerConfiguration
#Binding var pickerResult: [UIImage]
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> PHPickerViewController {
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
/// PHPickerViewControllerDelegate => Coordinator
class Coordinator: PHPickerViewControllerDelegate {
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { (newImage, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.parent.pickerResult.append(newImage as! UIImage)
}
}
} else {
print("Loaded Assest is not a Image")
}
}
// dissmiss the picker
parent.isPresented = false
}
}
}
struct photoPickerDemo_Previews: PreviewProvider {
static var previews: some View {
PhotoPickerDemo()
}
}
or if you want to crop via user interface like attach picture
Step 1
Using Xcode 12, go to File -> Swift Packages -> Add Package Dependency and enter https://github.com/marshallino16/ImageCropper
Step 2
in your didFinishPicking method where you are receiving selected image pass it in this package using these lines
let ratio = CropperRatio(width: 1, height: 1)//square ratio for crop
ImageCropperView(image: Image(yourSelectedImageHere),cropRect: nil,ratio: ratio).onCropChanged { (newCrop) in
print(newCrop)//here you will receive cropped image
}
edited use of ImageCropperView
struct PhotoPicker: UIViewControllerRepresentable {
let configuration: PHPickerConfiguration
#Binding var pickerResult: [UIImage]
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> PHPickerViewController {
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
/// PHPickerViewControllerDelegate => Coordinator
class Coordinator: PHPickerViewControllerDelegate {
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { (newImage, error) in
if let error = error {
print(error.localizedDescription)
} else {
let ratio = CropperRatio(width: 1, height: 1)//square ratio for crop
ImageCropperView(image: Image(newImage),cropRect: nil,ratio: ratio).onCropChanged { (newCrop) in
print(newCrop)//here you will receive cropped image
}
}
}
} else {
print("Loaded Assest is not a Image")
}
}
// dissmiss the picker
parent.isPresented = false
}
}
}

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?