I have a PHPIckerViewController which is available since iOS 14. And I want to get image from gallery which is format WEBP. But item provider in PHPicker can't load image with this format. Please tell me how can I pick and set image on UIButton with new picker.
code:
extension SixStepRegistrationViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
let supportedRepresentations = [UTType.rawImage.identifier,
UTType.tiff.identifier,
UTType.bmp.identifier,
UTType.png.identifier,
UTType.jpeg.identifier,
UTType.webP.identifier,
]
for representation in supportedRepresentations {
if results[0].itemProvider.hasRepresentationConforming(toTypeIdentifier: representation, fileOptions: .init()) {
print(representation, " repr")
results[0].itemProvider.loadInPlaceFileRepresentation(forTypeIdentifier: representation) { (originalUrl, inPlace, error) in
DispatchQueue.main.async {
print(originalUrl, " ", inPlace)
self.addPhotoButton.setImage(UIImage(contentsOfFile: originalUrl!.path), for: .normal)
//self.dismiss(animated: true, completion: nil)
}
}
}
}
}
Thanks
You should use itemProvider.loadDataRepresentation to load webp image:
import PhotosUI
class Coordinator: PHPickerViewControllerDelegate {
init(handler: #escaping (UIImage) -> Void) {self.handler = handler}
let handler: (UIImage) -> Void
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for itemProvider in results.map({$0.itemProvider}) {
if itemProvider.hasItemConformingToTypeIdentifier(UTType.webP.identifier) {
itemProvider.loadDataRepresentation(forTypeIdentifier: UTType.webP.identifier) {data, err in
if let data = data, let img = UIImage.init(data: data) {
self.handler(img)
}
}
} else {
itemProvider.loadObject(ofClass: UIImage.self) {reading, err in
if let img = reading as? UIImage {
self.handler(img)
}
}
}
}
}
}
After many experiments I found a solution.
use "loadDataRepresentation" instead of "loadInPlaceFileRepresentation" so you can get data and build an image.
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
let supportedRepresentations = [UTType.rawImage.identifier,
UTType.tiff.identifier,
UTType.bmp.identifier,
UTType.png.identifier,
UTType.jpeg.identifier,
UTType.webP.identifier,
]
for representation in supportedRepresentations {
if results[0].itemProvider.hasRepresentationConforming(toTypeIdentifier: representation, fileOptions: .init()) {
results[0].itemProvider.loadDataRepresentation(forTypeIdentifier: representation) { (data, err) in
DispatchQueue.main.async {
let img = UIImage(data: data!)
self.addPhotoButton.setImage(img, for: .normal)
}
}
}
}
}
Set PHPickerConfiguration to:
var config = PHPickerConfiguration(photoLibrary: .shared())
config.preferredAssetRepresentationMode = .current <====
Set this configuartion in your PHPickerController:
let picker = PHPickerViewController(configuration: config)
and then inside func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) Delegate callback:
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult])
let provider = results[index].itemProvider
provider.loadDataRepresentation(forTypeIdentifier: "public.image", completionHandler: { photoData, error in
if error == nil, let photoData = photoData, let photo = UIImage(data: photoData){
}else{
}
})
}
Related
My question is, how can I deselect(or empty the selection) after finishing uploading.
Right now, image upload successfully and display at HomeView as well. But when I try just to click share button it upload again the last selection. How can I fix that?
PhotoPicker:
struct PhotoPicker: UIViewControllerRepresentable {
#Binding var image: UIImage
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.allowsEditing = true
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) { }
func makeCoordinator() -> Coordinator {
return Coordinator(photoPicker: self)
}
final class Coordinator:NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let photoPicker: PhotoPicker
init(photoPicker: PhotoPicker){
self.photoPicker = photoPicker
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.editedImage] as? UIImage {
guard let data = image.jpegData(compressionQuality: 1), let compressedImage = UIImage(data: data) else {
return
}
photoPicker.image = compressedImage
} else {
}
picker.dismiss(animated: true)
}
}
}
Function for persistImage:
func persistUserInformation(){
guard let uid = Auth.auth().currentUser?.uid else
{ return }
let ref = Storage.storage().reference(withPath: uid)
guard let data = self.image.jpegData(compressionQuality: 0.5) else { return }
ref.putData(data, metadata: nil) { metadata, err in
if let err = err {
print("There was an error while putting data \(err) " )
return
}
ref.downloadURL { url, error in
if let error = error {
print("There was an error while downloading the data.")
return
}
print("Successfully uploaded image to storage! \(url?.absoluteString ?? "")")
quoteVM.addAQuote(quote: self.quoteField, imageUrl: url!)
}
}
}
I was expecting to refresh the last selection, and leave it empty until another one.
Below is my Picker code. It works great, but the full res image is too much -- causing smaller Image views be grainy. How can I add compression to the final result? Thanks!
import PhotosUI
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
#Binding var image: UIImage?
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration()
config.filter = .images
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, PHPickerViewControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let provider = results.first?.itemProvider else { return }
if provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { image, _ in
self.parent.image = image as? UIImage
}
}
}
}
}
You can try "jpegData(compressionQuality: 1)"
if provider.canLoadObject(ofClass: UIImage.self) {
provider.loadObject(ofClass: UIImage.self) { image, _ in
if let uiImage = selectedImage as? UIImage {
uiImage.jpegData(compressionQuality: 1)
self.parent.image = uiImage
}
}
}
Iam using PHPickerViewController to load multiple images and store them to CoreData.
Along with the imagepicker, there are three more parameters, which are successfully passed from a previous tableview on tapping its cell.
In coredata i have created an additional parameter galleryImage, of type NSObject to pass this images as array
//button tap to select picker
#IBAction func gallerySelectClicked(_ sender: Any) {
presentPickerView()
}
//configure
func presentPickerView() {
var config : PHPickerConfiguration = PHPickerConfiguration()
config.selectionLimit = 10
config.filter = PHPickerFilter.images
let pickerViewController = PHPickerViewController(configuration: config)
pickerViewController.delegate = self
self.present(pickerViewController, animated: true, completion: nil)
}
//Saving
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true, completion: nil)
for item in results {
item.itemProvider.loadObject(ofClass: UIImage.self, completionHandler: { (image, err) in
if let image = image as? UIImage {
print(image)
DispatchQueue.main.async {
self.Gallery.image = image
let dh = DatabaseHandler()
//Databasehandler in another file
dh.imgArray.append(image)
dh.saveImage()
}
}
})
}
}
Saveimage function in Databasehandler
func saveImage() {
let appDe = (UIApplication.shared.delegate) as! AppDelegate
let context = appDe.persistentContainer.viewContext
let photo = NSEntityDescription.insertNewObject(forEntityName: "People", into: context) as! People
var CDataArray = NSMutableArray();
for img in imgArray{
let data : NSData = NSData(data: img.jpegData(compressionQuality: 1)!)
CDataArray.add(data);
}
photo.galleryImage = NSKeyedArchiver.archivedData(withRootObject: CDataArray) as NSObject;
do{
try context.save()
print("data saved" )
} catch{
print("error")
}
}
Problem is when I select 2 images and print count to check wheather it is saved, output will be 2. when I select another 2 images in a second run, output is 4 . In this way, count keeos increasing
Could you please help me to find whats wrong with this code? I couldnt find any other good reference for PHPickerController .
I've got a problem with updating data. As you can see on this picture below, when I change those three textfields but leave current image and tap "Save", it will update my data in Firebase but also will upload a NEW image which is EMPTY so user will see an empty UIImageView.
Why is that?
Change Medication view image
Change Medication view no image
This is my method from Firebase to save image(data) and download URL:
func saveImageToStorage(cellImage: Data, completion: #escaping(Result<String, Error>) -> Void) {
guard let uid = Auth.auth().currentUser?.uid else { return }
refStorage.child(uid).child(imageName).putData(cellImage, metadata: nil) { (_, error) in
guard error == nil else {
completion(.failure(NSError(domain: "Saving image to storage failed", code: 0)))
return
}
self.refStorage.child(uid).child(self.imageName).downloadURL { (url, error) in
guard let url = url, error == nil else { return }
let urlString = url.absoluteString
completion(.success(urlString))
print("URL downloaded: \(urlString)")
}
}
}
Here is extension with UIImagePickerControllerDelegate:
extension CurrentMedicationSettingsViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let compressionQualityValue: CGFloat = 0.1
let image = info[.originalImage] as? UIImage
userMedicationSettingView.medicationImageView.image = image
if let uploadData = image?.jpegData(compressionQuality: compressionQualityValue) {
imageData = uploadData
}
picker.dismiss(animated: true, completion: nil)
}
ViewModel where update medication method is:
func updateMedicationInfo(data: Data, pillName: String, capacity: String, dose: String, childId: String, completion: #escaping () -> Void) {
firebaseManager.saveImageToStorage(cellImage: data) { (result) in
switch result {
case .failure(let error):
print(error.localizedDescription)
case .success(let url):
self.firebaseManager.updateMedicationInfo(pillName: pillName, capacity: capacity, dose: dose, cellImageURL: url, childId: childId)
}
completion()
}
}
Thanks for any advice!
Cheers!
The code here allows me to upload and download one photo to Firebase and save it to user defaults but I'm trying to figure out how to do it within a collectionView cell and display as many photos wanted, adding on new items
import UIKit
import FirebaseStorage
class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
private let storage = Storage.storage().reference()
#IBOutlet var imageView: UIImageView!
#IBOutlet var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
label.numberOfLines = 0
guard let urlString = UserDefaults.standard.value(forKey: "url") as? String, let url = URL(string: urlString) else {
return
}
label.text = urlString
let task = URLSession.shared.dataTask(with: url, completionHandler: { data,_,error in
guard let data = data, error == nil else {
return
}
DispatchQueue.main.async {
let image = UIImage(data: data)
self.imageView.image = image
}
})
task.resume()
}
#IBAction func didTapButton() {
let picker = UIImagePickerController()
picker.sourceType = .photoLibrary
picker.delegate = self
picker.allowsEditing = true
present(picker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion: nil)
guard let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage else {
return
}
guard let imageData = image.pngData() else {
return
}
storage.child("Images/Photo.png").putData(imageData, metadata: nil) { (_, error) in
guard error == nil else {
print("Failed to Upload Data")
return
}
self.storage.child("Images/Photo.png").downloadURL(completion: {url, error in
guard let url = url, error == nil else {
return
}
let urlString = url.absoluteString
DispatchQueue.main.async {
self.label.text = urlString
self.imageView.image = image
}
print("Download URL: \(urlString)")
UserDefaults.standard.set(urlString, forKey: "url")
})
}
// Upload Image Data
// Get Download URL
// Save Download URL to userDefaults
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
}
To upload images to Firebase storage and show them in a collection view, you can use the following steps;
Set up collection view with an array of URLs (or Strings) as its
data source. You can use your custom models if required.
Keep a reference to your Firebase storage and upload the image. After successful upload, get the URL for the uploaded image using the image reference.
Save the url in Firebase Database(or Cloud Firestore). This is required only if you want to sync the collection view with the database and update it when new images are uploaded.
Add a listener to your Firebase database reference where you have
saved the image URLs. Update the local URLs array inside the listener and reload the collection view.
If you don't want to use Firebase database, omit steps 3 and 4, save the image URL to the array and reload the collection view right away.
I'm not adding the code for collection view setup here as it's not the objective of this answer.
let storageRef = Storage.storage().reference(withPath: "images")
let databaseRef = Database.database().reference(withPath:"images")
var images: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
addDatabaseListener()
}
private func addDatabaseListener() {
databaseRef.observe(.childAdded) { (snapshot) in
guard let value = snapshot.value as? [String: Any], let url = value["url"] as? String else { return }
self.images.append(url)
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true)
guard let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage, let data = image.jpegData(compressionQuality: 0.1) else { return }
let fileName = "\(Date.timeIntervalSinceReferenceDate).jpeg"
let newImageRef = storageRef.child(fileName)
newImageRef.putData(data, metadata: nil) { (_, error) in
if let error = error {
print("upload failed: ", error.localizedDescription)
return
}
newImageRef.downloadURL { (url, error) in
if let error = error {
print("error: ", error.localizedDescription)
return
}
self.databaseRef.childByAutoId().setValue(["url": url?.absoluteString])
}
}
}