I want to move a PHAsset from one Album to another album. Here is what I am doing:
func saveImage(image: UIImage, album: PhotoAlbum, completion: (PHFetchResult?)->()) {
var placeholder: PHObjectPlaceholder?
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
// Request creating an asset from the image
let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
// Request editing the album
guard let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: album) else {
assert(false, "Album change request failed")
return
}
// Get a placeholder for the new asset and add it to the album editing request
guard let photoPlaceholder = createAssetRequest.placeholderForCreatedAsset else {
assert(false, "Placeholder is nil")
return
}
placeholder = photoPlaceholder
albumChangeRequest.addAssets([photoPlaceholder])
}, completionHandler: { success, error in
guard let placeholder = placeholder else {
assert(false, "Placeholder is nil")
completion(nil)
return
}
if success {
completion(PHAsset.fetchAssetsWithLocalIdentifiers([placeholder.localIdentifier], options: nil))
}
else {
print(error)
completion(nil)
}
})
}
The problem is that it creates a copy of it to destination rather than moving. At last, I ended up getting same images in different Albums.
Related
I am having a lot of trouble downloading pictures from URL for my tableview cells. I tried both synchronously and asynchronously downloading, but none of them worked.
For synchronous download, Xcode gives me purple warnings and the pictures won't show up in my tableview.
Warning:
"Synchronous URL loading of ... should not occur on this application's main thread as it may lead to UI unresponsiveness. Please switch to an asynchronous networking API such as URLSession."
For asynchronous download, the code after the downloading executes right away, and the download is not able to finish and results in a nil.
What should I do?
My code:
(I am loading tableview in batches of 15 posts, this is the code of loading batches)
func reloadBatch(){
for i in currentRow...currentRow+15{
if i == posts.count{
return
}
let post = posts[i] // documens
if post.posttype == 1{
let uP = UIImage(url: URL(string: post.userphoto!)) ?? UIImage(named: "Jordan")
postCell.append(LoadedCellModel(posttype: 1, sender: post.sender, userphoto: uP, title: post.title, photo: nil, videoURL: nil, content: post.content))
}else if post.posttype == 2{
let uP = UIImage(url: URL(string: post.userphoto!)) ?? UIImage(named: "Jordan")
let pic = UIImage(url: URL(string: post.photo![0])) ?? UIImage(named: "Jordan")
// This is the picture that does not show up, "photo" is an array of pictures' URL(in string)
postCell.append(LoadedCellModel(posttype: 2, sender: post.sender, userphoto: uP, title: post.title, photo: pic, videoURL: nil, content: post.content))
print(pic)
}
}
currentRow += 15
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
extension UIImage {
convenience init?(url: URL?) {
guard let url = url else { return nil }
do {
self.init(data: try Data(contentsOf: url))
} catch {
print("Cannot load image from url: \(url) with error: \(error)")
return nil
}
}
}
Getting data such as an image from the net is asynchronous.
That is, it takes time to do. So you need to "wait" until the data is available
before you can use it. Note the warning message ...Please switch to an asynchronous networking API such as URLSession
There are many ways to do this. Here I present a simple
way, using a function with a completion handler.
This means, you cannot use this function (getImage(url: ..)) like you are trying to do in:
let uP = UIImage(url: URL(string: post.userphoto!)) ?? UIImage(named: "Jordan")
You have to use the closure:
getImage(url: url) { img in
// do something with img
}
Here is an example code for downloading one image:
struct ContentView: View {
#State var uimg: UIImage?
let token = "xxxx" // <-- your secret token
var body: some View {
VStack {
if uimg == nil {
Text("downloading")
ProgressView()
} else {
Image(uiImage: uimg!).resizable().frame(width: 333, height: 333)
}
}
.onAppear {
guard let url = URL(string: "https://firebasestorage.googleapis.com/v0/b/pirateforum-f2f04.appspot.com/o/images%2F1663116366.2403781.png?alt=media&token=\(token)") else { return }
getImage(url: url) { img in
self.uimg = img
}
}
}
func getImage(url: URL, completion: #escaping (UIImage?) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, let img = UIImage(data: data) {
completion(img)
} else {
completion(nil)
}
}.resume()
}
}
P.S. do not show your secret token in the warning message, remove it.
I'm making an app whereby users post 2 images. I'm using Firebase for storage and as my database.
In my method to upload the images what I had wanted to do was to essentially use this method to return the URLs separately as well. I had written the following:
private func uploadImage(image: UIImage) -> URL? {
let randomName = UUID()
let storageRef = storage.reference().child("\(randomName)/png")
guard let uploadData = image.pngData() else { return nil}
var imageUrl: URL?
storageRef.putData(uploadData, metadata: nil) { (metadata, error) in
if error != nil {
print(error?.localizedDescription)
return
}
storageRef.downloadURL { (url, error) in
if error != nil {
print(error?.localizedDescription)
} else {
imageUrl = url
}
}
}
return imageUrl
}
And then I wrote the following 'post' method which is run when the submit button is tapped:
#objc func post() {
if let question = questionText.text,
let hashtagText = hashtagTextField.text,
let userHandle = Auth.auth().currentUser?.email,
let firstImage = left.image,
let secondImage = right.image,
let firstImageURL = uploadImage(image: firstImage)?.absoluteString,
let secondImageURL = uploadImage(image: secondImage)?.absoluteString
{
db.collection("posts").addDocument(data: [
"firstImage" : firstImageURL,
"secondImage" : secondImageURL,
"question" : question,
"hashtagText" : hashtagText,
"userHandle" : userHandle
]) { (error) in
if let e = error {
print("There was an issue saving data to Firestore, \(e)")
} else {
print("Successfully saved data")
self.dismiss(animated: true, completion: nil)
}
}
}
}
However, obviously the first method is not going to work as the closure is run after imageUrl is returned, therefore returning nil.
I've been trying to figure out how to manage this scenario - I had considered using a loop to populate an array of images but this got messy and I'm sure it is not the standard way to handle this. Any help would be greatly appreciated.
The return imageUrl is in the wrong place. It will return before Firebase has had time to store the image and return the url.
Additionally, the name of the file is not going to work. You currently have
storage.reference().child("\(randomName)/png") // xxxxx/png?
when it should be
storage.reference().child("\(randomName).png") // xxxxx.png
You can't 'return' data from a Firebase closure because firebase is asynchronous - a completion handler may possibly be a solution, but we don't know what the total use case is.
Let's assume you want want to store a users vacation picture in storage and then store that url in Firestore
private func uploadImage(image: UIImage) {
guard let uid = Auth.auth().currentUser?.uid else { return } //this users uid
let storageRef = storage.reference().child(uid).child("vacation.png")
//the path will be storage/users uid/vacation.png
guard let uploadData = image.pngData() else { return nil}
storageRef.putData(uploadData, metadata: nil) { (metadata, error) in
if error != nil {
print(error?.localizedDescription)
return
}
storageRef.downloadURL { (url, error) in
if error != nil {
print(error?.localizedDescription)
} else {
if url != nil {
//it's here where we store the imageUrl in Firestore
let dict = ["theUrl": url?.absoluteURL)]
let userRef = self.db.collection("users").document(uid)
//self.db points to *my* Firestore
userRef.collection("my_pics").addDocument(data: dict)
//will store in firstore/users/uid/docId/theUrl: the url
}
}
}
}
}
I am working in the edit profile portion of my application. When I try to change and update a users profile photo. The app crashes and I get this error
reason: 'URL scheme must be one of gs://, http://, or https://
When I create a new profile and add a profile photo or if I upload a photo it works fine but when I try to change the profile photo I get this. It will first remove the profile photo and update ( leaving the image view gray when a user doesn't have a photo) then when I try to rechange the photo again it will crash.
Here is the code I have.
func updateProfileImage() {
guard imageChanged == true else { return }
guard let currentUid = Auth.auth().currentUser?.uid else { return }
guard let user = self.user else { return }
Storage.storage().reference(forURL: user.profileImageUrl).delete(completion: nil)
let filename = NSUUID().uuidString
guard let updatedProfileImage = profileImageView.image else { return }
guard let imageData = updatedProfileImage.jpegData(compressionQuality: 0.3) else { return }
STORAGE_PROFILE_IMAGES_REF.child(filename).putData(imageData, metadata: nil) { (metadata, error) in
if let error = error {
print("Failed to upload image to storage with error: ", error.localizedDescription)
}
STORAGE_PROFILE_IMAGES_REF.downloadURL(completion: { (url, error) in
USER_REF.child(currentUid).child("profileImageUrl").setValue(url?.absoluteString, withCompletionBlock: { (err, ref) in
guard let userProfileController = self.userProfileController else { return }
userProfileController.fetchCurrentUserData()
self.dismiss(animated: true, completion: nil)
})
})
}
}
}
The first thing you check URL is valid or not using a guard.
guard let urlis = yourUrl else{
// url is nill.
return
}
if let url = NSURL(string: urlis) {
// your image code
}
else{
// url is invalid.
return
}
Add Exception Breakpoint: This quick tip will save you a lot of debugging time!. So Xcode will stop where the exception is caught.
In your project, go to the Breakpoint Navigator, click on the ’+’ button and ’Add Exception Breakpoint…’
I have a tableView with cells and inside every cell, we have cell.imageView?.image.
My task - upload by URL directly to that cell.imageView?.image image from google drive! I have completed link with file id like https://docs.google.com/uc?id=FILE-ID
I've tried the code below, but 0 effects!
import Foundation
for name in filesShow {
if name.name == i.textLabel?.text {
i.detailTextLabel?.text = name.identifier
i.accessoryType = .checkmark
i.imageView?.image = UIImage(url:
URL(string: "https://docs.google.com/uc?id=\(name.identifier)"))
self.tableView.reloadData()
continue
}
extension UIImage {
convenience init?(url: URL?) {
guard let url = url else { return nil }
do {
let data = try Data(contentsOf: url)
self.init(data: data)
} catch {
print("Cannot load image from url: \(url) with error: \(error)")
return nil
}
}
}
When using an image picker to grab the image selected and place in firebase storage I want to be able to download the URL and take place of the profile image inside the app. Unfortunately, when the process reaches the URLSession in the script nothing happens. There is not an error display nor does it dispatchQueue. The app will not crash but just skip over everything. Any ideas or suggestions to a code fix?
if let profileImageUploadedData = self.profileImage.image, let uploadData = profileImage.image?.jpegData(compressionQuality: 0.1)
{
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil
{
print("Downdloading putData Error: \(error!.localizedDescription)")
return
}
storageRef.downloadURL(completion: { (url, error) in
if error != nil
{
print("DownloadURL ERROR \(error!.localizedDescription)")
return
}
if let profileImageUrl = url?.absoluteString
{
print("Profile image uploading...")
let values = ["profileImageUrl": profileImageUrl]
let url = URL(fileURLWithPath: profileImageUrl)
URLSession.shared.dataTask(with: url) { (data, response, error) in // ERROR OCCURING NEED TO FIX
if error != nil
{
print("* URL SESSIONS ERROR: \(error!)")
return
}
DispatchQueue.main.async
{
print("Trying to register profile")
self.registerUserIntoDatabaseWithUID(uid: uid, values: values as [String : AnyObject])
self.profileImage.image = UIImage(data: data!)
print("Dispatch: \(data!)")
}
print("Profile image successfull uploaded to storage")
}
}
})
}).resume()
print("** Profile Image Data Uploaded:\(profileImageUploadedData)")
}
}
func registerUserIntoDatabaseWithUID(uid: String, values: [String: AnyObject])
{
print("Registering to database")
let dataReference = Database.database().reference(fromURL: "URL String")
let usersReference = dataReference.child("users").child(uid)
usersReference.updateChildValues(values, withCompletionBlock: { (err, reference) in
if err != nil
{
print(err!)
return
}
// self.profileImage.image = values["profileImageUrl"] as? UIImage
// self.fetchProfileImage()
self.dismiss(animated: true, completion: nil)
print("Saved user sussccessfully in database")
})
}
}
This is a big question that is actually asking for a number of different answers so let's just focus on one;
How to authenticate a user, get a url stored in Firebase Database that
references an image stored in Firebase Storage, then download that
image.
Here we go
First - authenticate a user
Auth.auth().signIn(withEmail: user, password: pw, completion: { (auth, error) in
if let x = error {
//handle an auth error
} else {
if let user = auth?.user {
let uid = user.uid
self.loadUrlFromFirebaseDatabase(withUid: uid)
}
}
})
now the user is authenticated, get the image location url from Firebase Database
func loadUrlFromFirebaseDatabase(withUid: String) {
let thisUserRef = self.ref.child("users").child(withUid)
let urlRef = thisUserRef.child("url")
urlRef.observeSingleEvent(of: .value, with: { snapshot in
if let url = snapshot.value as? String {
self.loadImageUsingUrl(url: url)
} else {
print("no image for user")
}
})
}
Now that we have the location of the image in Firebase Storage, get it
func loadImageUsingUrl(url: String) {
let storage = Storage.storage()
let imageRef = storage.reference(forURL: url)
imageRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
print("error downloading \(error)")
} else {
if let image = UIImage(data: data!) {
//do something with the image
} else {
print("no image")
}
}
}
}