How to store multiple images in NSUserDefaults picked through share extension - ios

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

Related

get Image from local path in simulator

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?

TableView with labels, images, gifs and video hangs / gets stuck incorrect while fetch from firestore in iOS, Swift

I have tableview with label, imageView (for image, gif & video thumbnail). I am sure that doing something wrong and I can't handle its completion handler due to which the app is hanged and gets stuck for a long time.
My model is like,
struct PostiisCollection {
var id :String?
var userID: String?
var leadDetails : NSDictionary?
var company: NSDictionary?
var content: String?
init(Doc: DocumentSnapshot) {
self.id = Doc.documentID
self.userID = Doc.get("userID") as? String ?? ""
self.leadDetails = Doc.get("postiiDetails") as? NSDictionary
self.company = Doc.get("company") as? NSDictionary
self.content = Doc.get("content") as? String ?? ""
}
}
I wrote in my view controller for fetch this,
var postiisCollectionDetails = [PostiisCollection]()
override func viewDidLoad() {
super.viewDidLoad()
let docRef = Firestore.firestore().collection("PostiisCollection").whereField("accessType", isEqualTo: "all_access")
docRef.getDocuments { (querysnapshot, error) in
if let doc = querysnapshot?.documents, !doc.isEmpty {
print("Document is present.")
for document in querysnapshot!.documents {
_ = document.documentID
if let compCode = document.get("company") as? NSDictionary {
do {
let jsonData = try JSONSerialization.data(withJSONObject: compCode)
let companyPost: Company = try! JSONDecoder().decode(Company.self, from: jsonData)
if companyPost.companyCode == AuthService.instance.companyId ?? ""{
print(AuthService.instance.companyId ?? "")
if (document.get("postiiDetails") as? NSDictionary) != nil {
let commentItem = PostiisCollection(Doc: document)
self.postiisCollectionDetails.append(commentItem)
}
}
} catch {
print(error.localizedDescription)
}
DispatchQueue.main.async {
self.tableView.isHidden = false
self.tableView.reloadData()
}
}
}
}
}
}
I need to check for the index path with image view is either image or gif or video with different parameters, I tried with tableview delegate and datasource method by,
extension AllAccessPostiiVC: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postiisCollectionDetails.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AllAccessCell", for: indexPath)
let label1 = cell.viewWithTag(1) as? UILabel
let imagePointer = cell.viewWithTag(3) as? UIImageView
let getGif = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "gif") as? NSArray
let getPhoto = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "photo") as? NSArray
let getVideo = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "video") as? NSArray
label1?.text = "\(arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "title"))"
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
print(arrGif[0])
let gifURL : String = "\(arrGif[0])"
let imageURL = UIImage.gifImageWithURL(gifURL)
imagePointer?.image = imageURL
playButton?.isHidden = true
}
if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
print(arrPhoto[0])
let storageRef = Storage.storage().reference(forURL: arrPhoto[0])
storageRef.downloadURL(completion: { (url, error) in
do {
let data = try Data(contentsOf: url!)
let image = UIImage(data: data as Data)
DispatchQueue.main.async {
imagePointer?.image = image
playButton?.isHidden = true
}
} catch {
print(error)
}
})
}
if getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let videoURL = URL(string: arrVideo[0])
let asset = AVAsset(url:videoURL!)
if let videoThumbnail = asset.videoThumbnail{
SVProgressHUD.dismiss()
imagePointer?.image = videoThumbnail
playButton?.isHidden = false
}
}
}
}
If I run, the app hangs in this page and data load time is getting more, some cases the preview image is wrongly displayed and not able to handle its completion
As others have mentioned in the comments, you are better of not performing the background loading in cellFroRowAtIndexPath.
Instead, it's better practice to add a new method fetchData(), where you perform all the server interaction.
So for example:
// Add instance variables for fast access to data
private var images = [UIImage]()
private var thumbnails = [UIImage]()
private func fetchData(completion: ()->()) {
// Load storage URLs
var storageURLs = ...
// Load data from firebase
let storageRef = Storage.storage().reference(forURL: arrPhoto[0])
storageRef.downloadURL(completion: { (url, error) in
// Parse data and store resulting image in image array
...
// Call completion handler to indicate that loading has finished
completion()
})
}
Now you can call fetchData() whenever you would like to refresh data and call tableview.reloadData() within the completion handler. That of course must be done on the main thread.
This approach simplifies your cellForRowAtIndexPath method.
There you can just say:
imagePointer?.image = ...Correct image from image array...
Without any background loading.
I suggest using below lightweight extension for image downloading from URL
using NSCache
extension UIImageView {
func downloadImage(urlString: String, success: ((_ image: UIImage?) -> Void)? = nil, failure: ((String) -> Void)? = nil) {
let imageCache = NSCache<NSString, UIImage>()
DispatchQueue.main.async {[weak self] in
self?.image = nil
}
if let image = imageCache.object(forKey: urlString as NSString) {
DispatchQueue.main.async {[weak self] in
self?.image = image
}
success?(image)
} else {
guard let url = URL(string: urlString) else {
print("failed to create url")
return
}
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
// response received, now switch back to main queue
DispatchQueue.main.async {[weak self] in
if let error = error {
failure?(error.localizedDescription)
}
else if let data = data, let image = UIImage(data: data) {
imageCache.setObject(image, forKey: url.absoluteString as NSString)
self?.image = image
success?(image)
} else {
failure?("Image not available")
}
}
}
task.resume()
}
}
}
Usage:
let path = "https://i.stack.imgur.com/o5YNI.jpg"
let imageView = UIImageView() // your imageView, which will download image
imageView.downloadImage(urlString: path)
No need to put imageView.downloadImage(urlString: path) in mainQueue, its handled in extension
In your case:
You can implement following logic in cellForRowAt method
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
let urlString : String = "\(arrGif[0])"
let image = UIImage.gifImageWithURL(urlString)
imagePointer?.image = image
playButton?.isHidden = true
}
else if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
let urlString = Storage.storage().reference(forURL: arrPhoto[0])
imagePointer?.downloadImage(urlString: urlString)
playButton?.isHidden = true
}
elseif getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let urlString = arrVideo[0]
imagePointer?.downloadImage(urlString: urlString)
playButton?.isHidden = false
}
If you have one imageView to reload in tableView for photo, video and gif. then use one image array to store it prior before reloading. So that your main issue of hang or stuck will be resolved. Here the main issue is each time in table view cell collection data is being called and checked while scrolling.
Now I suggest to get all photo, gifs and video (thumbnail) as one single array prior to table view reload try this,
var cacheImages = [UIImage]()
private func fetchData(completionBlock: () -> ()) {
for (index, _) in postiisCollectionDetails.enumerated() {
let getGif = postiisCollectionDetails[index].leadDetails?.value(forKey: "gif") as? NSArray
let getPhoto = postiisCollectionDetails[index].leadDetails?.value(forKey: "photo") as? NSArray
let getVideo = postiisCollectionDetails[index].leadDetails?.value(forKey: "video") as? NSArray
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
let gifURL : String = "\(arrGif[0])"
self.randomList.append(gifURL)
/////---------------------------
let imageURL = UIImage.gifImageWithURL(gifURL)
self.cacheImages.append(imageURL!)
//////=================
}
else if getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let videoURL: String = "\(arrVideo[0])"
let videoUrl = URL(string: arrVideo[0])
let asset = AVAsset(url:videoUrl!)
if let videoThumbnail = asset.videoThumbnail{
////--------------
self.cacheImages.append(videoThumbnail)
//-----------
}
self.randomList.append(videoURL)
}else if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
let photoURL : String = "\(arrPhoto[0])"
/////---------------------------
let url = URL(string: photoURL)
let data = try? Data(contentsOf: url!)
if let imageData = data {
let image = UIImage(data: imageData)
if image != nil {
self.cacheImages.append(image!)
}
else {
let defaultImage: UIImage = UIImage(named:"edit-user-80")!
self.cacheImages.append(defaultImage)
}
}
//////=================
}
else {
//-----------------
let defaultImage: UIImage = UIImage(named:"edit-user-80")!
self.cacheImages.append(defaultImage)
//--------------------
}
}
completionBlock()
}
After getting all UIImage as array where loop is being called. Now you call this function inside your viewDidLoad. So after all values in images fetched then try to call tableView like this,
override func viewDidLoad() {
self.fetchData {
DispatchQueue.main.async
self.tableView.reloadData()
}
}
}
Now atlast, you may use SDWebImage or any other background image class or download method to call those in tableView cellforRow method,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// your cell idetifier & other stuffs
if getVideo != nil {
imagePointer?.image = cacheImages[indexPath.row]
playButton?.isHidden = false
}else {
imagePointer?.image = cacheImages[indexPath.row]
// or get photo with string via SdWebImage
// imagePointer?.sd_setImage(with: URL(string: photoURL), placeholderImage: UIImage(named: "edit-user-80"))
playButton?.isHidden = true
}
return cell
}
You're handling data in a totally wrong manner. Data(contentsOf: url!) - This is wrong. You should chache the images and should download it to directory. When you convert something into data it takes place into the memory(ram) and it is not good idea when playing with large files. You should use SDWebImage kind of library to set images to imageview.
Second thing if let videoThumbnail = asset.videoThumbnail - This is also wrong. Why you're creating assets and then getting thumbnail from it? You should have separate URL for the thumbnail image for your all videos in the response of the API and then again you can use SDWebImage to load that thumbnail.
You can use SDWebImage for gif as well.
Alternative of SDWebImage is Kingfisher. Just go through both libraries and use whatever suitable for you.

How to populate an UICollectionView cell with images from Firebase Storage?

I'm building an app that needs an image slideshow as the background of the welcome controller. My plan is to import images into a folder in Firebase Storage, set a Service function to download the folder's images and append to a model, then populate the controller's collection view cell with the images. Am I on the correct path to do an image slideshow? Thanks.
// BackgroundImage Model
struct BackgroundImage {
let backgroundImageUrl: String
init(dictionary: [String : Any]) {
self.backgroundImageUrl = dictionary["backgroundImageUrl"] as? String ?? ""
}
}
// Service
struct BgImgs {
let backgroundImage: UIImage
}
static func fetchBackgroundImages(bgImgs: BgImgs, completion: #escaping([BackgroundImage]) -> Void) {
var backgroundImages = [BackgroundImage]()
guard let imageData = bgImgs.backgroundImage.jpegData(compressionQuality: 0.3) else { return }
let filename = NSUUID().uuidString
let storageRef = STORAGE_REF.reference(withPath: "/background_images/\(filename)")
storageRef.putData(imageData, metadata: nil) { (meta, error) in
storageRef.downloadURL { (url, error) in
guard let backgroundImageUrl = url?.absoluteString else { return }
let values = ["backgroundImageUrl" : backgroundImageUrl]
let bgImages = BackgroundImage(dictionary: values)
backgroundImages.append(bgImages)
completion(backgroundImages)
}
}
}
// WelcomeController
private var backgroundImages = [BackgroundImage]()
func fetchBackgroundImages() {
Service.fetchBackgroundImages(bgImgs: backgroundImages) { backgroundImages in
self.backgroundImages = backgroundImages
}
}
You'll have to reload the UICollectionView after fetchBackgroundImages fetch is done.
func fetchBackgroundImages() {
Service.fetchBackgroundImages(bgImgs: backgroundImages) { backgroundImages in
self.backgroundImages = backgroundImages
self.collectionView.reloadData()
}
}

iOS custom share extension: load selected image

I'm working on a share extension for my app, to be able to use it for sharing images directly from the library. I have a view controller and layout set up, but I'm struggling with getting the selected image to show in the UIImageView inside the view controller.
For now, my getImage() is as follows:
func getImage() {
if let inputItem = extensionContext!.inputItems.first as? NSExtensionItem {
if let itemProvider = inputItem.attachments?.first as? NSItemProvider {
itemProvider.loadItem(forTypeIdentifier: kUTTypeJPEG as String) { [unowned self] (imageData, error) in
if let item = imageData as? Data {
self.imageView.image = UIImage(data: item)
}
}
}
}
}
...but the image is not loading. What am I doing wrong here?
Found the problem. Turned out I needed to do a type identifier conformance check first:
func getImage() {
if let inputItem = extensionContext!.inputItems.first as? NSExtensionItem {
if let itemProvider = inputItem.attachments?.first as? NSItemProvider {
// This line was missing
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeJPEG as String) {
itemProvider.loadItem(forTypeIdentifier: kUTTypeJPEG as String) { [unowned self] (imageData, error) in
if let item = imageData as? Data {
self.imageView.image = UIImage(data: item)
}
}
}
}
}
}

How to receive an image from cloudkit?

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.

Resources