I am using this code to store an image in icloud, but what code do i use to retrieave it and place it in a UIImageView? I've tried everything, but it wont work?
func SaveImageInCloud(ImageToSave: UIImage) {
let newRecord:CKRecord = CKRecord(recordType: "ImageRecord")
let nsDocumentDirectory = NSSearchPathDirectory.DocumentDirectory
let nsUserDomainMask = NSSearchPathDomainMask.UserDomainMask
if let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true) {
if paths.count > 0 {
if let dirPath = paths[0] as? String {
let writePath = dirPath.stringByAppendingPathComponent("Image2.png")
UIImagePNGRepresentation(ImageToSave).writeToFile(writePath, atomically: true)
var File : CKAsset? = CKAsset(fileURL: NSURL(fileURLWithPath: writePath))
newRecord.setValue(File, forKey: "Image")
}
}
}
if let database = self.privateDatabase {
database.saveRecord(newRecord, completionHandler: { (record:CKRecord!, error:NSError! ) in
if error != nil {
NSLog(error.localizedDescription)
} else {
dispatch_async(dispatch_get_main_queue()) {
println("finished")
}
}
})
}
Just read the CKRecord that you wrote and you can get the CKAsset by reading the key Image. You can get a UIImage using the code below.
var file : CKAsset? = record.objectForKey("Image")
func image() -> UIImage? {
if let file = file {
if let data = NSData(contentsOfURL: file.fileURL) {
return UIImage(data: data)
}
}
return nil
}
After downloading the CKAsset, we need to convert the CKAsset to a UIImage. We can use the following extension (Swift 4 code):
extension CKAsset {
func toUIImage() -> UIImage? {
if let data = NSData(contentsOf: self.fileURL) {
return UIImage(data: data as Data)
}
return nil
}
}
You have to first have a way of finding the specific ImageRecord that you want to retrieve. Assuming that you have the RecordID for the ImageRecord you saved (you can get this from the record in the saveRecord completion block) you can do:
if let database = privateDatabase {
database.fetchRecordWithID(recordID, completionHandler: { (record, error) -> Void in
guard let record = record else {
print("Error retrieving record", error)
return
}
guard let asset = record["Image"] as? CKAsset else {
print("Image missing from record")
return
}
guard let imageData = NSData(contentsOfURL: asset.fileURL) else {
print("Invalid Image")
return
}
let image = UIImage(data: imageData)
imageView.image = image
})
}
(Although you would definitely want to be doing some error handling where those print()s are)
If you don't save the recordID (or probably better: the recordID.recordName so you can make another CKRecordID later), you would need some other way of finding which record you are looking for. If that's the case you'd want to look into using CKDatabase's performQuery(_:inZoneWithID:completionHandler:) method.
Related
I'm trying to get the UIImage from the local URL, but it doesn't work.
My someURl, where data is NSSecureCoding?: file:///Users/a19468455/Library/Developer/CoreSimulator/Devices/2FA5B725-9266-429C-A036-256D846BC3FA/data/Media/PhotoData/OutgoingTemp/3F73D172-AA12-40AD-A3AB-540543A43330/IMG_0004.JPG
Here's what I've tried (sacrificed code styling for convenience):
guard let inputItems = extensionContext?.inputItems as? [NSExtensionItem else { return }
inputItems.forEach { item in
if let attachments = item.attachments,
!attachments.isEmpty {
attachments.forEach { attachment in
if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
var image: UIImage?
attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { [weak self] data, error in
guard let self = self else { return }
guard error == nil else {
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
return
}
if let someURl = data as? URL {
do {
let data = try Data(contentsOf: someURl)
image = UIImage(data: data)
} catch {
print("failed to retrieve data")
}
}
if let image = image {
images.append(image)
}
}
}
}
}
}
I also tried tips from here without success:
how to load image from local path ios swift (by path)
how to load image from local image path Ios Swift 4
Loading/Downloading image from URL on Swift
I have no way to get Data from my link, how do I fix it?
I currently did for single image which is working fine. But i want to pick multiple images and display in app.Can any one please help me in this. Thank you.
override func didSelectPost() {
if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
let contentType = kUTTypeImage as String
if let contents = content.attachments as? [NSItemProvider] {
for attachment in contents {
if attachment.hasItemConformingToTypeIdentifier(contentType) {
attachment.loadItemForTypeIdentifier(contentType, options: nil) { data, error in
let url = data as! NSURL
if let imageData = NSData(contentsOfURL: url) {
self.saveImage(imageData)
}
}
}
}
}
}
// Unblock the UI.
self.extensionContext!.completeRequestReturningItems([], completionHandler: nil)
}
Saves an image to user defaults
func saveImage(imageData: NSData) {
if let prefs = NSUserDefaults(suiteName: suiteName) {
prefs.setObject(imageData, forKey: keyName)
}
}
Reading in app
if let prefs = NSUserDefaults(suiteName: suiteName) {
if let imageData = prefs.objectForKey(keyName) as? NSData {
drawCard(imageData)
}
}
And i modified this for array, but its not working.
override func didSelectPost() {
if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
let contentType = kUTTypeImage as String
if let contents = content.attachments as? [NSItemProvider] {
for attachment in contents {
if attachment.hasItemConformingToTypeIdentifier(contentType) {
attachment.loadItemForTypeIdentifier(contentType, options: nil) { data, error in
let url = data as! NSURL
if let imageData = NSData(contentsOfURL: url) {
galleryBucket[url] = imageData
self.saveImage(galleryBucket)
}
}
}
}
}
}
// Unblock the UI.
self.extensionContext!.completeRequestReturningItems([], completionHandler: nil)
}
Saves an image to user defaults.
func saveImage(galleryBucket: [NSURL:NSData]) {
if let prefs = NSUserDefaults(suiteName: suiteName) {
prefs.setObject(galleryBucket, forKey: keyName)
}
}
Here I am getting nil for galleryBucket
if let prefs = NSUserDefaults(suiteName: suiteName) {
if let galleryBucket = prefs.objectForKey(keyName) as? [NSURL:NSData] {
for imageData in galleryBucket{
drawCard(imageData.1)
}
}
}
UserDefaults doesn't seem like the best place for saving images..
Take a look at this:
Save An Image To Application Documents Folder From UIView On IOS
And this is how you can search for a specific file extenstion.
Getting list of files in documents folder
And once you have a name, this is how you retrieve it.
Get image from documents directory swift
I am trying to complet an an action after the URLSession resumes.
So I am downloading several images from my server with the url, which all works good. But now I am trying to save those images to the disk after I have finished downloading them.
Problem
Now I can save them inside the same query while downloading them but I would prefer not too as it makes my query slower.
So I have added a completion handler to my func with the query, but when I save the images to the disk in that block it works but I cannot do anything with my screen as the query has not resumed yet it is blocked from touches I guess...
Now I would like to be able to call my func to save the images to the disk straight after the query has been resumed.... Anyone have any idea?
If someone needs more explanation or to see code just drop a comment below
Many thanks in advance to anyone that can help!
Code for downloading
func loadPosts(completionHandler: #escaping (Bool) -> ()) {
pageNumber = 1
appDelegate.setNetworkActivityIndicatorVisible(true)
let id = user!["id"] as! String
let url = URL(string: "http://************/Files/Posts.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let body = "id=\(id)&caption=&uuid=&page="
request.httpBody = body.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data:Data?, response:URLResponse?, error:Error?) in
DispatchQueue.global(qos: .background).async {
if error == nil {
let oldImageArray = self.cellContentArray
self.cellContentArray.removeAll(keepingCapacity: false)
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
guard let parseJSON = json else {
print("Error while parsing")
return
}
guard let posts = parseJSON["Posts"] as? [AnyObject] else {
print("Error while parseJSONing")
return
}
for element in posts {
// here I download the stuff and it to my Array, too long and no point to show here
}
let oldImageSet = Set(oldImageArray.map({return $0.uuid}))
let newImageSet = Set(self.cellContentArray.map({return $0.uuid}))
let addedImages = newImageSet.subtracting(oldImageSet)
let addedImageSections = Array(addedImages).map{ self.cellContentArray.map({return $0.uuid}).index(of: $0)! }
let addedImageIndexSet = IndexSet(addedImageSections)
let removedImages = oldImageSet.subtracting(newImageSet)
let removedImageSections = Array(removedImages).map{ oldImageArray.map({return $0.uuid}).index(of: $0)! }
let removedImageIndexSet = IndexSet(removedImageSections)
if !addedImageIndexSet.isEmpty {
if oldImageArray.count >= 5 {
self.lastUUIDImage = oldImageArray[4].uuid
} else {
}
self.coreDataShit()
}
DispatchQueue.main.async{
print(placeholderImage.count)
if placeholderImage.count > 5 {
placeholderImage.removeFirst(placeholderImage.count - 5)
}
print("finished")
self.customView.isHidden = true
if posts.count >= 5 {
self.tableView.addInfiniteScroll { [weak self] (scrollView) -> Void in
self?.loadMore()
}}
self.activityView.stopAnimating()
self.internetView.removeFromSuperview()
self.tableView.beginUpdates()
if !addedImageIndexSet.isEmpty {
self.tableView.insertSections(addedImageIndexSet, with: .top)
}
if !removedImageIndexSet.isEmpty {
self.tableView.deleteSections(removedImageIndexSet, with: .bottom)
}
self.tableView.endUpdates()
self.tableView.finishInfiniteScroll()
self.refresher.endRefreshing()
appDelegate.setNetworkActivityIndicatorVisible(false)
completionHandler(true)
}
} catch {
DispatchQueue.main.async {
self.tableView.removeInfiniteScroll()
self.customView.isHidden = false
self.refresher.endRefreshing()
self.tableView.reloadData()
}
}
} else {
DispatchQueue.main.async(execute: {
let message = error!.localizedDescription
appDelegate.infoView(message: message, color: smoothRedColor)
})
}
}
})
task.resume()
}
Saving Image
self.loadPosts(completionHandler: { (true) in
print("completion")
let sections = self.tableView.numberOfSections
for i in 0..<sections {
self.rows += self.tableView.numberOfRows(inSection: i)
}
print(self.rows)
if self.rows <= 5 {
print("less than 5")
print(self.rows)
var i = 0
for element in self.cellContentArray {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let dirPath = "\(path)/images"
let url = NSURL(fileURLWithPath: dirPath)
let filePath = url.appendingPathComponent("\(element.uuid).jpg")?.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath!) {
print("File exsists")
} else {
print("File doesn't exsist")
DispatchQueue.main.async {
let url = NSURL(string: element.fullImage!)! // convert path str to url
let imageData = NSData(contentsOf: url as URL) // get data via url and assigned imageData
let imageName = element.uuid
let saveImages = FileSaveHelper(fileName: imageName, fileExtension: .JPG, subDirectory: "images", directory: .documentDirectory)
do {
guard let image = UIImage.sd_image(with: imageData as Data!) else {
print("Error getting image")
return
}
try saveImages.saveFile(image: image)
self.saveNewImagePath(imageLink: imageName, uuid: imageName)
self.removeImage(itemName: "file\(i)", fileExtension: "jpg")
self.removeImage(itemName: self.lastUUIDImage, fileExtension: "jpg")
i += 1
} catch {
print(error)
}
}
}
}
}
})
Image in tableView Cell
self.postImage.sd_setImage(with: URL(string: content.fullImage!), placeholderImage: placeHolder, options: .retryFailed) { (image:UIImage?, error:Error?, cached:SDImageCacheType, url:URL?) in
}
In your code of saving image this line of code is blocking you UI
let imageData = NSData(contentsOf: url as URL) // get data via url and assigned imageData
This is not proper way to download image from server, you should download image asynchronously using URLSession
I've recently been working on a project that uploads and downloads files through the Amazon S3 service, specifically a user uploaded profile picture. I am also using Amazon Cognito (Username, full name, etc), and one of the things I like about Cognito is that it saves the downloaded data locally on the phone and displays it even when the app is offline. Then, when the app goes online, it updates any data changes. Unfortunately, the same doesn't happen with S3. Every time the app starts, it has to download the profile picture from my S3 bucket before displaying them. What I want to do is have the app save the user's profile picture from the S3 bucket on the iOS device so the user can see their picture even when the app goes offline. Here is what I currently have:
func downloadImage(){
var completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock?
//downloading image
let S3BucketName: String = "*******"
let S3DownloadKeyName: String = self.credentialsProvider.identityId + "/profileImage.png"
let expression = AWSS3TransferUtilityDownloadExpression()
expression.downloadProgress = {(task: AWSS3TransferUtilityTask, bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) in
dispatch_async(dispatch_get_main_queue(), {
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
// self.statusLabel.text = "Downloading..."
NSLog("Progress is: %f",progress)
})
}
completionHandler = { (task, location, data, error) -> Void in
dispatch_async(dispatch_get_main_queue(), {
if ((error) != nil){
NSLog("Failed with error")
NSLog("Error: %#",error!);
//self.statusLabel.text = "Failed"
} else {
//self.statusLabel.text = "Success"
self.userProfile.image = UIImage(data: data!)
self.profileBackground.image = UIImage(data: data!)
}
})
}
let transferUtility = AWSS3TransferUtility.defaultS3TransferUtility()
transferUtility.downloadToURL(nil, bucket: S3BucketName, key: S3DownloadKeyName, expression: expression, completionHander: completionHandler).continueWithBlock { (task) -> AnyObject! in
if let error = task.error {
NSLog("Error: %#",error.localizedDescription);
// self.statusLabel.text = "Failed"
}
if let exception = task.exception {
NSLog("Exception: %#",exception.description);
// self.statusLabel.text = "Failed"
}
if let _ = task.result {
// self.statusLabel.text = "Starting Download"
NSLog("Download Starting!")
// Do something with uploadTask.
}
return nil;
}
}
Thanks in advance!
This is a helper to download an image from S3 Bucket and save it on local phone
next time if you seek to this image. the helper will check if a local copy exists if not it will download from S3
import UIKit
import AWSCore
import AWSS3
class S3Helper: NSObject {
#objc var completionHandler: AWSS3TransferUtilityDownloadCompletionHandlerBlock?
#objc lazy var transferUtility = {
AWSS3TransferUtility.default()
}()
let DocDirPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
func downloadImage(imageView: UIImageView!, S3Folder: String, S3FileName: String) {
let S3DownloadKeyName: String = S3Folder + "/" + S3FileName
imageView.image = UIImage(named: "ic_place_holder.pdf")
let filePath = DocDirPath.appendingPathComponent(S3FileName).path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
let imageURL = self.DocDirPath.appendingPathComponent(S3FileName)
imageView.image = UIImage(contentsOfFile: imageURL.path)!
} else {
let expression = AWSS3TransferUtilityDownloadExpression()
expression.progressBlock = {(task, progress) in
DispatchQueue.main.async(execute: {
})
}
self.completionHandler = { (task, location, data, error) -> Void in
DispatchQueue.main.async(execute: {
if let error = error {
NSLog("Failed with error: \(error)")
//self.statusLabel.text = "Failed"
}
else{
//self.statusLabel.text = "Success"
imageView.image = UIImage(data: data!)
do{
let imgPath = URL(fileURLWithPath: self.DocDirPath.appendingPathComponent(S3FileName).path)
try (imageView.image ?? UIImage(named: "ic_place_holder.pdf")!).jpegData(compressionQuality: 1.0)?.write(to: imgPath, options: .atomic)
} catch let error1{
print(error1.localizedDescription)
}
}
})
}
transferUtility.downloadData(
forKey: S3DownloadKeyName,
expression: expression,
completionHandler: completionHandler).continueWith { (task) -> AnyObject? in
if let error = task.error {
NSLog("Error: %#",error.localizedDescription);
DispatchQueue.main.async(execute: {
//self.statusLabel.text = "Failed"
})
}
if let _ = task.result {
DispatchQueue.main.async(execute: {
//self.statusLabel.text = "Downloading..."
})
NSLog("Download Starting!")
// Do something with uploadTask.
}
return nil;
}
}
}
}
How do I upload and load back images from cloud kit with swift?
What attribute type do I use?
What code do I use? This is the code I use currently...
func SaveImageInCloud(ImageToSave: UIImage) {
let newRecord:CKRecord = CKRecord(recordType: "ImageRecord")
newRecord.setValue(ImageToSave, forKey: "Image")
if let database = self.privateDatabase {
database.saveRecord(newRecord, completionHandler: { (record:CKRecord!, error:NSError! ) in
if error != nil {
NSLog(error.localizedDescription)
}
else {
dispatch_async(dispatch_get_main_queue()) {
println("finished")
}
}
})
}
You need to create a CKAsset and add that to your record. You can do that with code like this:
func SaveImageInCloud(ImageToSave: UIImage) {
let newRecord:CKRecord = CKRecord(recordType: "ImageRecord")
let nsDocumentDirectory = NSSearchPathDirectory.DocumentDirectory
let nsUserDomainMask = NSSearchPathDomainMask.UserDomainMask
if let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true) {
if paths.count > 0 {
if let dirPath = paths[0] as? String {
let writePath = dirPath.stringByAppendingPathComponent("Image2.png")
UIImagePNGRepresentation(ImageToSave).writeToFile(writePath, atomically: true)
var File : CKAsset? = CKAsset(fileURL: NSURL(fileURLWithPath: writePath))
newRecord.setValue(File, forKey: "Image")
}
}
}
if let database = self.privateDatabase {
database.saveRecord(newRecord, completionHandler: { (record:CKRecord!, error:NSError! ) in
if error != nil {
NSLog(error.localizedDescription)
} else {
dispatch_async(dispatch_get_main_queue()) {
println("finished")
}
}
})
}
Here's something similar to Edwin's answer but a little more compact. I've tested this and it works well.
This example is saving "myImage" UIImageView into "mySaveRecord" CKRecord, just replace those names with your respective ones.
let documentDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let imageFilePath = documentDirectory.stringByAppendingPathComponent("lastimage")
UIImagePNGRepresentation(myImage).writeToFile(imageFilePath, atomically: true)
let asset = CKAsset(fileURL: NSURL(fileURLWithPath: imageFilePath))
mySaveRecord.setObject(asset, forKey: "ProfilePicture")
CKContainer.defaultContainer().publicCloudDatabase.saveRecord(mySaveRecord, completionHandler: {
record, error in
if error != nil {
println("\(error)")
} else {
//record saved successfully!
}
})
I created this little extension in Swift 5 to convert from UIImage to CKAsset:
extension UIImage {
func toCKAsset(name: String? = nil) -> CKAsset? {
guard let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else {
return nil
}
guard let imageFilePath = NSURL(fileURLWithPath: documentDirectory)
.appendingPathComponent(name ?? "asset#\(UUID.init().uuidString)")
else {
return nil
}
do {
try self.pngData()?.write(to: imageFilePath)
return CKAsset(fileURL: imageFilePath)
} catch {
print("Error converting UIImage to CKAsset!")
}
return nil
}
}
You can then use it as you have it in your question:
if let asset = ImageToSave.toCKAsset() {
newRecord.setObject(asset, forKey: "Image")
CKContainer.defaultContainer().publicCloudDatabase.saveRecord(newRecord, completionHandler: {
record, error in
if error != nil {
println("\(error)")
} else {
// saved successfully!
}
})
}
This answer works with Swift 2.2 & iOS 9, and separates the file creation from the upload so that you can properly test against both, since they are distinct actions with their own potential issues.
For the uploadPhoto function, the recordType variable is the value you use in your CloudKit dashboard. The "photo" key in the photo["photo"] = asset line is the field name for your record type.
func uploadPhoto(image: UIImage, recordName: String) {
let privateDB = CKContainer.defaultContainer().privateCloudDatabase
let photoID = CKRecordID(recordName: recordName)
let photo = CKRecord(recordType: recordType, recordID: photoID)
let asset = CKAsset(fileURL: writeImage(image))
photo["photo"] = asset
privateDB.saveRecord(photo) { (record, error) in
guard error == nil else {
print(error?.localizedDescription)
return
}
print("Successful")
}
}
func writeImage(image: UIImage) -> NSURL {
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
let fileURL = documentsURL.URLByAppendingPathComponent(NSUUID().UUIDString + ".png")
if let imageData = UIImagePNGRepresentation(image) {
imageData.writeToURL(fileURL, atomically: false)
}
return fileURL
}
You can call this with the following:
uploadPhoto(UIImage(named: "foo.png")!, recordName: "bar")
You'll want to pick the Asset value type in the dashboard for this value.
newRecord.setValue(ImageToSave, forKey: "Image")
UIImage is not an allowed type on CKRecord. Your best option is to write this image out to a file, then create a CKAsset and set that on the record.