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
}
}
}
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.
I am trying to display rich links in a SwiftUI List and no matter what I try, I can't seem to be able to change the size of the link view (UIViewRepresentable) on screen.
Is there a minimum size for a particular link? And how can I get it. Adding .aspectRatio and clipped() will respect size but the link is heavily clipped. Not sure why the link will not adjust aspectRatio to fit view.
Some of the following code is sourced from the following tutorial:
https://www.appcoda.com/linkpresentation-framework/
I am using the following UIViewRepresentable for the LinkView:
import SwiftUI
import LinkPresentation
struct LinkViewRepresentable: UIViewRepresentable {
typealias UIViewType = LPLinkView
var metadata: LPLinkMetadata?
func makeUIView(context: Context) -> LPLinkView {
guard let metadata = metadata else { return LPLinkView() }
let linkView = LPLinkView(metadata: metadata)
return linkView
}
func updateUIView(_ uiView: LPLinkView, context: Context) {
}
}
And my view with List is:
import SwiftUI
import LinkPresentation
struct ContentView: View {
#ObservedObject var linksViewModel = LinksViewModel()
var links: [(String, String)] = [("https://www.apple.com", "1"), ("https://www.stackoverflow.com", "2")]
var body: some View {
ScrollView(.vertical) {
LazyVStack {
ForEach(links, id: \.self.1) { link in
VStack {
Text(link.0)
.onAppear {
linksViewModel.getLinkMetadata(link: link)
}
if let richLink = linksViewModel.links.first(where: { $0.id == link.1 }) {
if let metadata = richLink.metadata {
if metadata.url != nil {
LinkViewRepresentable(metadata: metadata)
.frame(width: 200) // setting frame dimensions here has no effect
}
}
}
}
}
}
.padding()
}
}
}
Setting the frame of the view or contentMode(.fit) or padding or anything else I've tried does not change the size of the frame of the LinkViewRepresentable. I have tried sizeToFit in the representable on update and no luck. Is it possible to control the size of the representable view here?
Here are additional Files:
import Foundation
import LinkPresentation
class LinksViewModel: ObservableObject {
#Published var links = [Link]()
init() {
loadLinks()
}
func createLink(with metadata: LPLinkMetadata, id: String) {
let link = Link()
link.id = id
link.metadata = metadata
links.append(link)
saveLinks()
}
fileprivate func saveLinks() {
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: links, requiringSecureCoding: true)
guard let docDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
try data.write(to: docDirURL.appendingPathComponent("links"))
print(docDirURL.appendingPathComponent("links"))
} catch {
print(error.localizedDescription)
}
}
fileprivate func loadLinks() {
guard let docDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let linksURL = docDirURL.appendingPathComponent("links")
if FileManager.default.fileExists(atPath: linksURL.path) {
do {
let data = try Data(contentsOf: linksURL)
guard let unarchived = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [Link] else { return }
links = unarchived
} catch {
print(error.localizedDescription)
}
}
}
func fetchMetadata(for link: String, completion: #escaping (Result<LPLinkMetadata, Error>) -> Void) {
guard let uRL = URL(string: link) else { return }
let metadataProvider = LPMetadataProvider()
metadataProvider.startFetchingMetadata(for: uRL) { (metadata, error) in
if let error = error {
print(error)
completion(.failure(error))
return
}
if let metadata = metadata {
completion(.success(metadata))
}
}
}
func getLinkMetadata(link: (String, String)) {
for storedLink in self.links {
if storedLink.id != link.1 {
return
}
}
do {
let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: link.0, options: [], range: NSRange(location: 0, length: link.0.utf16.count))
if let match = matches.first {
guard let range = Range(match.range, in: link.0) else { return }
let uRLString = link.0[range]
self.fetchMetadata(for: String(uRLString)) { result in
self.handleLinkFetchResult(result, link: link)
}
}
} catch {
print(error)
}
}
private func handleLinkFetchResult(_ result: Result<LPLinkMetadata, Error>, link: (String, String)) {
DispatchQueue.main.async {
switch result {
case .success(let metadata):
self.createLink(with: metadata, id: link.1)
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
And Link Class:
import Foundation
import LinkPresentation
class Link: NSObject, NSSecureCoding, Identifiable {
var id: String?
var metadata: LPLinkMetadata?
override init() {
super.init()
}
// MARK: - NSSecureCoding Requirements
static var supportsSecureCoding = true
func encode(with coder: NSCoder) {
guard let id = id, let metadata = metadata else { return }
coder.encode(id, forKey: "id")
coder.encode(metadata as NSObject, forKey: "metadata")
}
required init?(coder: NSCoder) {
id = coder.decodeObject(forKey: "id") as? String
metadata = coder.decodeObject(of: LPLinkMetadata.self, forKey: "metadata")
}
}
This is what I get:
The solution that worked for me was subclassing the linkView overriding the intrinsic content size. Thanks to user1046037's comment, using super.intrinsicContentSize.height will enable it to work dynamically.
import SwiftUI
import LinkPresentation
class CustomLinkView: LPLinkView {
override var intrinsicContentSize: CGSize { CGSize(width: 0, height: super.intrinsicContentSize.height) }
}
struct LinkViewRepresentable: UIViewRepresentable {
typealias UIViewType = CustomLinkView
var metadata: LPLinkMetadata?
func makeUIView(context: Context) -> CustomLinkView {
guard let metadata = metadata else { return CustomLinkView() }
let linkView = CustomLinkView(metadata: metadata)
return linkView
}
func updateUIView(_ uiView: CustomLinkView, context: Context) {
}
}
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{
}
})
}
I have an image picker and after the image got picked, it should get uploaded into my Firebase Storage.
Now I have one problem: Somehow all the images dont get cropped correctly.
Did I misplace some Code, because that's the only thing I could think of, since I already got
picker.allowsEditing = true
The picture gets uploaded correctly, but not how I intended to crop it. The "cropper-window" shows up, but it doesnt get saved as intended.
Here is my code:
import UIKit
import Firebase
class editViewController: UIViewController {
#IBOutlet var pfp: UIImageView!
var fireImage: UIImage? = nil
override func viewDidLoad() {
super.viewDidLoad()
setupPfp()
}
func setupPfp(){
pfp.layer.cornerRadius = pfp.frame.height/2
pfp.clipsToBounds = true
pfp.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(presentPicker))
pfp.addGestureRecognizer(tapGesture)
}
#objc func presentPicker(){
let picker = UIImagePickerController()
picker.sourceType = .photoLibrary
picker.allowsEditing = true
picker.delegate = self
self.present(picker, animated: true, completion: nil)
}
func uploadToFirebase(){
guard let imageSelected = self.fireImage else {
print("Image is nil")
return
}
guard let imageData = imageSelected.jpegData(compressionQuality: 0.5) else {
return
}
let storageRef = Storage.storage().reference(forURL: "gs://lidjd-9dad5.appspot.com")
let storageProfileRef = storageRef.child("profileImages").child(Auth.auth().currentUser!.uid)
let metadata = StorageMetadata()
let db = Firestore.firestore()
let userID = Auth.auth().currentUser!.uid
let docRef = db.collection("users").document(userID)
metadata.contentType = "image/jpeg"
storageProfileRef.putData(imageData, metadata: metadata) { (storageMetaData, error) in
if error != nil{
print(error!.localizedDescription)
return
}
storageProfileRef.downloadURL { (url, error) in
if let metaImageUrl = url?.absoluteString{
print(metaImageUrl)
docRef.updateData([
"profileimage": metaImageUrl
]){ err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Profile Image successfully updated")
}}
}
}
}
}
}
extension editViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate{
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let imageSelected = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
fireImage = imageSelected
pfp.image = imageSelected
}
if let imageOriginal = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
fireImage = imageOriginal
pfp.image = imageOriginal
}
picker.dismiss(animated: true, completion: nil)
uploadToFirebase()
}
}
Choose one of them .. in your current code you are always using orignal image
extension editViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate{
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let imageSelected = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
fireImage = imageSelected
pfp.image = imageSelected
}
else if let imageOriginal = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
fireImage = imageOriginal
pfp.image = imageOriginal
}
picker.dismiss(animated: true, completion: nil)
uploadToFirebase()
}
}
I am making a project very similiar to Instagram's multiple image post feature, and I have 2 errors that are connected to each other in my capturePhotoController because of my ImageUploadManager. The errors are:
Cannot assign to immutable expression of type 'ImageUploadManager.Type'
Type 'ImageUploadManager?' has no member 'uploadImage'
It is happening in this view controller: *The errors are marked with a comment
import UIKit
class takePicViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
#IBOutlet weak var imageView: UIImageView!
let imagePicker = UIImagePickerController()
override func viewDidLoad() {
super.viewDidLoad()
imagePicker.delegate = self
imagePicker.sourceType = .camera
imagePicker.allowsEditing = false
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let userPickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
// Error ImageUploadManager = ImageUploadManager()
// Error ImageUploadManager?.uploadImage(userPickedImage, progressBlock: { (percentage) in
print(percentage)
}, completionBlock: { (fileURL, errorMessage) in
print(fileURL)
print(errorMessage)
})
PhotoArray.sharedInstance.photosArray.append(userPickedImage) //append converted data in array
imageView.image = userPickedImage
}
imagePicker.dismiss(animated: true, completion: nil)
}
#IBAction func cameraButtonPressed(_ sender: UIButton) {
present(imagePicker, animated: true, completion: nil)
}
}
Here is the code for the ImageUploadManager:
import UIKit
import FirebaseStorage
import Firebase
struct Constants {
struct Car {
static let imagesFolder: String = "carImages"
}
}
class ImageUploadManager: NSObject {
func uploadImage(_ image: UIImage, /**/ progressBlock: #escaping (_ percentage: Double) -> Void, /**/ completionBlock: #escaping (_ url: String?, _ errorMessage: String?) -> Void) {
let data = Data()
let storage = Storage.storage()
let storageRef = storage.reference()
let imageName = "\(Date().timeIntervalSince1970).jpg"
let imagesReference = storageRef.child(Constants.Car.imagesFolder).child(imageName)
if let imageData = UIImageJPEGRepresentation(image, 0.8) {
let metadata = storageRef.child("image/jpg")
let uploadTask = metadata.putData(data, metadata: nil) { (metadata, error) in
guard let metadata = metadata else {
// Uh-oh, an error occurred!
return
}
storageRef.downloadURL { (url, error) in
guard let downloadURL = url else {
// Uh-oh, an error occurred!
return
}
}
}
uploadTask.observe(.progress, handler: { (snapshot) in
guard let progress = snapshot.progress else {
return
}
let percentage = (Double(progress.completedUnitCount) / Double(progress.totalUnitCount)) * 100
progressBlock(percentage)
})
} else {
completionBlock(nil, "Image could not be converted to Data.")
}
}
}
Is the commented code:
// Error ImageUploadManager = ImageUploadManager()
your declaration of ImageUploadManager? if so that is totally wrong. It should be declared as below:
if let userPickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
let imageUploadManager = ImageUploadManager()
imageUploadManager.uploadImage(userPickedImage, prog.....
Reading your ImageUploadManager it looks like it's an stateless object and you can declare your function as a class function.
`class func uploadImage(_ image: UIImage`......`
You can then call it:
if let userPickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
ImageUploadManager.uploadImage(userPickedImage, prog.....