Here is the code I use to download an image from an AWS S3 server and assign it to be the image displayed in an image view:
let s3BucketName = "bucketName"
let fileName = Globals.currAuthorName.filter { $0 != Character(" ") } + ".jpg"
let downloadFilePath = NSTemporaryDirectory().stringByAppendingPathComponent(fileName)
let downloadingFileURL = NSURL.fileURLWithPath(downloadFilePath)
// Create a credential provider for AWS requests
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType: AWSRegionType.USEast1,
identityPoolId: "us-east-1:********-****-****-****-************")
// Create a service configuration for AWS requests
let defaultServiceConfiguration = AWSServiceConfiguration(
region: AWSRegionType.USEast1,
credentialsProvider: credentialsProvider)
// Create a new download request to S3, and set its properties
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
let downloadRequest = AWSS3TransferManagerDownloadRequest()
downloadRequest.bucket = s3BucketName
downloadRequest.key = "folderName/" + fileName
downloadRequest.downloadingFileURL = downloadingFileURL
let transferManager = AWSS3TransferManager.defaultS3TransferManager()
transferManager.download(downloadRequest)
// Set the UIImageView to show the file that was downloaded
let image = UIImage(contentsOfFile: downloadFilePath)
authorImage.contentMode = UIViewContentMode.ScaleAspectFit
authorImage.image = image
The problem is that when I run the iOS Simulator and navigate to the appropriate page, the image is never loaded the first time I visit the page. However, it is always loaded when I navigate away from the page and then return the page, and every subsequent time I visit the page. The image is still loaded when I stop running the app and then start it again and visit the page again. The image is only not loaded the very first time I visit the page for a certain iOS Simulator (i.e. it happens once in the iPhone 6 simulator, the iPhone 5 simulator, etc)
I tried to fix the problem by adding this bit of code:
while (image == nil) {
transferManager.download(downloadRequest)
let image = UIImage(contentsOfFile: downloadFilePath)
}
But that simply resulted in an infinite while loop the first time I visited the page. Also the console eventually output this error:
2015-07-06 23:26:28.529 CCBF[19839:92323] 19839: CFNetwork internal error (0xc01a:/SourceCache/CFNetwork_Sim/CFNetwork-711.3.18/Foundation/NSURLRequest.mm:798)
2015-07-06 23:26:28.564 AuthorProject[19839:92323] AWSiOSSDKv2 [Error] AWSURLSessionManager.m line:240 | __41-[AWSURLSessionManager taskWithDelegate:]_block_invoke222 | Invalid AWSURLSessionTaskType.
EDIT:
I tried to implement the following continuewithblock:
var image : UIImage!
let task = transferManager.download(downloadRequest)
task.continueWithBlock({
if task.error != nil {
println(task.error)
} else {
dispatch_async(dispatch_get_main_queue(), {
image = UIImage(contentsOfFile: downloadFilePath)
self.authorImage.contentMode = UIViewContentMode.ScaleAspectFit
self.authorImage.image = image
})
}
return nil
}())
However, when I ran it and tried to load the image, I got a runtime error that pointed me to the code for a "continueWithExecutor" method. Specifically, the line of code:
result = block(self);
With the message being:
EXC_BAD_ACCESS(code: 1, address=0x10)
EDIT2:
I seem to have found a solution that works:
var image : UIImage!
let taskTODO = transferManager.download(downloadRequest)
taskTODO.continueWithBlock{ (task: AWSTask!) -> AnyObject! in
if task.error != nil {
println(task.error)
} else {
dispatch_async(dispatch_get_main_queue(), {
image = UIImage(contentsOfFile: downloadFilePath)
self.authorImage.contentMode = UIViewContentMode.ScaleAspectFit
self.authorImage.image = image
})
}
return nil
}
Does this seem like the correct way to do it?
transferManager.download(downloadRequest) is asynchronous, meaning it takes about 20 miliseconds to download the image. However, the first time into the screen you are trying to assign the image immediately (which has not yet been downloaded). In subsequent screen loads - the image has already been downloaded and cached - so it's immediately ready to be displayed, . What you need is a completion block that indicates when the image is has finished downloading and is ready for display and in that block you shall assign the image. See example below:
let readRequest1 : AWSS3TransferManagerDownloadRequest = AWSS3TransferManagerDownloadRequest()
readRequest1.bucket = "shrikar-picbucket"
readRequest1.key = "bingo"
readRequest1.downloadingFileURL = downloadingFileURL1
let task = transferManager.download(readRequest1)
task.continueWithBlock { (task) ->; AnyObject! in
println(task.error)
if task.error != nil {
} else {
dispatch_async(dispatch_get_main_queue()
, { () ->; Void in
self.selectedImage.image = UIImage(contentsOfFile: downloadingFilePath1)
self.selectedImage.setNeedsDisplay()
self.selectedImage.reloadInputViews()
})
println("Fetched image")
}
return nil
}
Related
Language : Swift 5
iOS: 13.2
macOS: Catalina 10.15.4
Firebase Storage Rules:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth!=null;
}
}
}
The code to upload image and save download URL: (Which works fine, because I can see images uploaded to storage and their respective download URLs stored to real-time database.)
let storageRef = Storage.storage().reference()
//Let's upload all workout pictures
let uploadPicsRef =
storageRef.child("WORKOUTDATA/USERS/"+self.UID!).child("WHITEBOARDWORKOUTS")
let uploadNumberRef = uploadPicsRef.child("\(String(describing: workoutNum))")
let workoutPicturesRef = uploadNumberRef.child("WORKOUTPICTURES")
let workoutPicURLRef = workoutRef.child("WORKOUTPICTURESURL")
var count = 0
var picNumber = 0
//workoutPictures list/array contains images selected from iPhone Gallery, using
//UIImagePickerController
for workoutPic in self.workoutPictures
{
let workoutPicData = workoutPic.jpegData(compressionQuality: 1.0)!
count = count + 1
let pictureName = "Picture\(count).jpg"
// Upload the file to the path in pictureRef
let pictureRef = workoutPicturesRef.child("\(pictureName)")
let metaData = StorageMetadata()
metaData.contentType = "image/jpg"
pictureRef.putData(workoutPicData, metadata: metaData) { (metadata, error) in
if error != nil {
print("Error while uploading image")
}
else
{
pictureRef.downloadURL { (url, err) in
picNumber = picNumber + 1
workoutPicURLRef.child("Picture\(picNumber)").setValue(url?.absoluteString)
}
}
}
}
The code to download image:
let myGroup = DispatchGroup()
let workoutPicUrls = snapshot.childSnapshot(forPath: "WORKOUTPICTURESURL")
for url in workoutPicUrls.children
{
myGroup.enter()
let snap = url as! DataSnapshot
let link = snap.value as? String
let storageRef = Storage.storage().reference()
let pictureRef = storageRef.root().child(link!)
DispatchQueue.main.async {
pictureRef.getData(maxSize: 1*2000000*2000000) { (data, err) in
if (err != nil) {
print(err!)
print(err!.localizedDescription)
} else {
let pic = UIImage(data: data!)
workoutPicsArray.append(pic!)
myGroup.leave()
}
}
}
}
Error:
Error Domain=FIRStorageErrorDomain Code=-13010 "Object https:/firebasestorage.googleapis.com/v0/b/trainer-8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547 does not exist." UserInfo={object=https:/firebasestorage.googleapis.com/v0/b/trainer-8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547, ResponseBody={
"error": {
"code": 404,
"message": "Not Found. Could not get object",
"status": "GET_OBJECT"
}
}, bucket=trainer-8cb52.appspot.com, data={length = 115, bytes = 0x7b0a2020 22657272 6f72223a 207b0a20 ... 54220a20 207d0a7d }, data_content_type=application/json; charset=UTF-8, NSLocalizedDescription=Object https:/firebasestorage.googleapis.com/v0/b/trainer-8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547 does not exist., ResponseErrorDomain=com.google.HTTPStatus, ResponseErrorCode=404}
What I have tried so far:
Checked firebase storage rules.
When I paste the path https:/firebasestorage.googleapis.com/v0/b/trainer8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547 in chrome browser window, the expected image opens.
Set the maxSize to a ridiculously high number 1*2000000*2000000.
Thank you!
Is it possible that you are storing the full https URL in the database and are trying to create a reference by adding the full https url as a child to the storage reference?
I think you should try to either store just the path and name in your database or you change your download code to use the https URL.
// Create a reference from an HTTPS URL
// Note that in the URL, characters are URL escaped!
let httpsReference = storage.reference(forURL: "https://firebasestorage.googleapis.com/b/bucket/o/images%20stars.jpg")
httpsReference.getData(maxSize: ...
Also you're running your getData method inside DispatchQueue.main.async. getData has itself a completion handler and might take some time, when you run that inside of DispatchQueue.main.async it will block your code until the download is done. Only put code that update the UI inside DispatchQueue.main.async. In your case as soon as you do something with your workoutPicsArray or the UIImage to update your view.
Have a look here to see if you can figure out how you are actually trying to get the data. It might be helpful to put a print() after each line to see what you are creating and using at what point.
Download Files on iOS
I'm trying to fetch image data from firebase storage with swift .
Code :
let ref = Storage.storage().reference().child("users").child("uid").child("savedimage");
let task = ref.getData(maxSize: 1024*1024*12) { (data, error) in
if let data = data , let image = UIImage(data: data) {
print("image exists");
self.imageView.image = image;
}else {
print(error);
}
}
task.resume();
But most of the time the app crash after a second of getting the image , and take me to this :
It's not showing any error in console output so i cannot figure out what's the issue but sometimes it's give me a warning before the crash :
warning: could not execute support code to read Objective-C class data in the process. This may reduce the quality of type information available.
What i'm doing wrong ?
I have a program in which the user chooses a photo to put on the screen and the code puts it into a custom album automatically. But whenever they choose a picture, it resaves it to the camera roll, creating duplicates. How do I make it stop doing this?
func fetchAssetCollectionForAlbum() -> PHAssetCollection? {
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %#", albumName)
// fetch the asset for the album
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
var picturePlaceHolder: PHObjectPlaceholder? = nil
if let _: AnyObject = collection.firstObject {
return collection.firstObject
}
return nil
}
func save(image: UIImage) {
if assetCollection == nil {
return
}
PHPhotoLibrary.shared().performChanges({
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: self.assetCollection)
let enumeration: NSArray = [assetPlaceHolder!]
albumChangeRequest!.addAssets(enumeration)
}, completionHandler: nil)
}
I posted a similar question:
Swift 3 or 4 Saving to custom album creates duplicate images
But I got nothing but crickets as well. Luckily, I think I found the answer. I'll answer my own question as well.
The code you have (which was the same code I had) is to CREATE A NEW ASSET. It is useful only for the saving the image to your custom album after the user has taken a picture with the camera. It is for brand new assets.
However, for existing assets, you do not want to create a new asset. Instead, you want to add the existing asset to the custom album. To do this, you need a different method. Here is the code I created and it seems to be working. Keep in mind that you will have to get the asset ID FIRST, so that you can send it to your method and access the existing asset.
So, in your imagePickerController, you have to determine whether the user chose an existing image or whether the method is being called from a new camera action.
let pickerSource = picker.sourceType;
switch(pickerSource){
case .savedPhotosAlbum, .photoLibrary:
if(let url = info[UIIMagePickerControllerReferenceURL] as? NSURL{
let refURLString = refURL?.absoluteString;
/* value for refURLString looks something like assets-library://asset/asset.JPG?id=82A6E75C-EA55-4C3A-A988-4BF8C7F3F8F5&ext=JPG */
let refID = {function here to extract the id query param from the url string}
/*above gets you the asset ID, you can get the asset directly, but it is only
available in ios 11+.
*/
MYPHOTOHELPERCLASS.transferImage(toAlbum: "myalbumname", withID: refID!, ...)
}
break;
case .camera:
...
break;
}
Now, in your photohelper class (or in any function anywhere, whatever), to EDIT the asset instead of create a new one, this is what I have. I am assuming the changeRequest variable can be ommitted. I was just playing around until I got this right. Going through the completely ridiculous apple docs I was able to at least notice that there were other methods to play with. I found that the NSFastEnumeration parameter can be an NSArray of PHAssets, and not just placeholder PHObjectPlaceholder objects.
public static func transferImage(toAlbum albumName:String, withID imageID:String, onSuccess success:#escaping(String)->Void, onFailure failure:#escaping(Error?)->Void){
guard let album = self.getAlbum(withName: albumName) else{
... failure here, albumNotFoundError
return;
}
if(self.hasImageInAlbum(withIdentifier: imageID, fromAlbum: albunName)){
... failure here, image already exists in the album, do not make another
return;
}
let theAsset = self.getExistingAsset(withLocalIdentifier: imageID);
if(theAsset == nil){
... failure, no asset for asset id
return;
}
PHPhotoLibrary.shared().performChanges({
let albumChangeRequest = PHAssetCollectionChangeRequest(for: album);
let changeRequest = PHAssetChangeRequest.init(for: theAsset!);
let enumeration:NSArray = [theAsset!];
let cnt = album.estimatedAssetCount;
if(cnt == 0){
albumChangeRequest?.addAssets(enumeration);
}else{
albumChangeRequest?.inserAssets(enumeration, at: [0]);
}
}){didSucceed, error) in
OperationQueue.main.addOperation({
didSucceed ? success(imageID) : failure(error);
})
}
}
So, it is pretty much the same, except instead of creating an Asset Creation Request and generating a placeholder for the created asset, you instead just use the existing asset ID to fetch an existing asset and add the existing asset to the addasset/insertasset NSArray parameter instead of a newly created asset placeholder.
I'm getting the following error when trying to download an image from my Firebase Storage:
Error Domain=FIRStorageErrorDomain Code=-13010 "Object 2xxxxxxx8/profile_pic does not exist."
(I obviously put the x's up there to mask private info.)
I'm adding a path reference to my Firebase Storage using the following code:
let storage = FIRStorage.storage()
let storageRef = storage.referenceForURL("gs://project-4xxxxxxxxxxxxx.appspot.com")
let profilePicReference = storageRef.child(signedInUser.uid + "/profile_pic")
I know the code above is good cause everything was working correctly: I could see a folder was added in my Storage space, and an image was uploaded into that folder - all directly from my iOS App.
The problems started when I manually deleted said folder from my Firebase Storage (I did this through the Firebase web portal) - just cause I wanted verify everything was working, so I deleted the folder to start fresh - expecting the code above would recreate it once I ran the App again - and since then I'm getting this error over and over again.
Really makes no sense.
Are there any quirks or issues with Firebase Storage? Some sort of caching that has to be addressed?
Any tips would be greatly appreciated!
Are there any quirks or issues with Firebase Storage? Some sort of
caching that has to be addressed?
An UploadTask executes asynchronously. If I try downloading an image immediately after uploading an image, I can reproduce your error. What's happening is that the download code executes before the image finishes uploading, producing the image-does-not-exist error. You can see that the download code executes too early by printing out some messages in the callbacks:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
That code produces the output:
[My Download Error]: ...."Object images/align_menu.tiff does not exist."...
and then after a few seconds I see the output:
[My Upload Success]:
[URL for download]: ...
which demonstrates that the download callback is executing before the upload callback. I can't quite figure out the details of why that happens--but obviously the callbacks are not added to a serial queue.*
To cure the asynchronous problem, you have several options:
1) Put the download code inside the callback for the upload code.
That way, the download won't start executing until after the image has successfully uploaded. After I did that, deleting the image using the Firebase Storage webpage before running the app had no deleterious effect on my upload/download, and the messages were output in the expected order:
[My Upload Success]:
[URL for download]: ...
[My Download Success]:
2) Attach a .Success observer to the uploadTask.
As described in the Firebase docs, in the Monitor Upload Progress section, you can get notified if the uploadTask successfully uploads the image:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
let observer = uploadTask.observeStatus(.Success) { (snapshot) -> Void in
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
3) Use Grand Central Dispatch to notify you when the upload is successful.
You don't have control over what queues the callbacks get added to (the Firebase method implementations decide that), but you can use Grand Central Dispatch to notify you when arbitrary code finishes executing. The following works for me:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
let myExecutionGroup = dispatch_group_create()
dispatch_group_enter(myExecutionGroup)
//Upload the image:
let _ = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
dispatch_group_leave(myExecutionGroup)
}
}
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
dispatch_group_notify(myExecutionGroup, queue) {
//This callback executes for every dispatch_group_leave().
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
* I tried putting a sleep(10) between the original upload code and download code, and that did not alleviate the problem. I thought that if the upload callback was executing on a background thread, then the upload callback would have time to complete while the main thread was sleeping, then after the sleep finished the download code would execute and the download callback would be added to a queue somewhere, then the download callback would execute. Because the sleep(10) didn't solve the problem, that meant the upload callback had to have been added to an execution queue for the main thread, and the sleep halted the main thread and anything in the queue from executing.
That leads me to believe that the upload and download callbacks are added to an asynchronous queue on the main thread (it's not a synchronous queue otherwise the callbacks would execute in order). I think an asynchronous queue on the main thread means that when there is dead time on the main thread, the tasks in the queue will execute, and you also get rapid switching between the various tasks when there is dead time in a particular task, like waiting for an HTTP response. For example, if there are two tasks in an asynchronous queue on the main thread, then there is rapid switching between the main thread, task1, and task2 whenever there is dead time in any one of them.
I am programming a Swift application, and I can't load image saved in the application by using UIImage(contentsOfFile: imgPath) or NSData(contentsOfFile: imgPath)
private func loadData() {
println("load DATA DANGEROUS")
let (dangerous, err) = SD.executeQuery("SELECT * FROM Dangerous")
if err == nil && !dangerous.isEmpty {
var tabPhoto : [DangerousImage] = []
for d in dangerous {
let desc = d["description"]!.asString()!
let idDangerous = d["id"]!.asInt()!
println("iddangerous : \(idDangerous)")
let (photos, error) = SD.executeQuery("SELECT * FROM Photo WHERE idDangerous = ?", withArgs: [idDangerous])
if error == nil {
for photo in photos {
let imgPath = photo["photoPath"]!.asString()!
println(imgPath)
let uimage = UIImage(contentsOfFile: imgPath) // fatal error: unexpectedly found nil while unwrapping an Optional value
tabPhoto.append(DangerousImage(img: uimage!, path: imgPath))
}
}
println("add ENTRY")
self.tabEntry.append(Entry(descript: desc, tab: tabPhoto))
}
}
println("TAB ENTRY : \(tabEntry)")
}
My picture exists with this path : /var/mobile/Containers/Data/Application/...ID-APP.../Documents/images/JPEG_201506162_101128_IOS_99804574.jpg
Thank for your help.
Ysee
From I can see in your code, you are storing the full path to the image in your database, eg. "/var/mobile/Containers/Data/Application/...ID-APP.../Documents/images/JPEG_201506162_101128_IOS_99804574.jpg". Since iOS8, the folder structure has changed - the UDID in the path is changing every time the app is updated or a new build is installed during development. That's why should store a relative path to your image, eg. "/images/JPEG_201506162_101128_IOS_99804574.jpg" and then get the Documents directory with NSSearchPathForDirectoriesInDomains method.