Prevent images to download again after going back or terminating app - swift

First of all I want to say, I didn't find anyting about it on this site.
I want to save NSURL into NSUserDefaults to prevent loading image on every app open or the view open. I tried to achieve it like this but for some reason I can't. At least I think thats what should do it:
let productImageref = productsValue[indexPath.row]["Products"] as? String
cell.snusProductImageView.image = nil
if let url = self.productsValue[indexPath.row]["imageUrl"] as? NSURL {
cell.snusProductImageView.kf_showIndicatorWhenLoading = true
var storedUrl = defaults.objectForKey("imageUrl") as? NSURL
cell.snusProductImageView.kf_setImageWithURL(storedUrl)
}
else {
FIRStorage.storage().reference().child("\(productImageref!).png").downloadURLWithCompletion({(url, error)in
if error != nil{
print(error)
return
}else{
self.productsValue[indexPath.row]["imageUrl"] = url
self.defaults.setURL(url, forKey: "imageUrl")
self.productstable.reloadData()
dispatch_async(dispatch_get_main_queue(), {
})
}
})
}
If I am wrong then please correct me. Should saving the url-s into userdefaults do the trick?
What exactly I am doing wrong here? Maybe it is better to save it into CoreData but I thought that it would be overkill.
Why the KingFisher or SDWebImages library is not caching it?

Two things that might help: use synchronize after saving something to defaults and use URLForKey to get it back.
(And check that the thing you're saving really is a NSURL.)
Here's sample code that works in a project; compare it to yours (NSUserDefaults is broken for playgrounds.):
let defaults = NSUserDefaults.standardUserDefaults()
if let components = NSURLComponents(string: "http://localhost/"),
url = components.URL {
defaults.setURL(url, forKey: "imageUrl")
defaults.synchronize()
if let fetchedURL = defaults.URLForKey("imageUrl") {
print("\(fetchedURL) returned")
} else {
print("No URL in defaults")
}
} else {
print("Bad components")
}
print(defaults.dictionaryRepresentation())

You have to store URL like below:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setURL(yourURL:NSURL?, forKey: "imageURL")
And read NSUserDefaults like this:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.URLForKey("imageURL")
Hope this helps.

As i understood, you want to save multiple images url in NSUserDefaults
let url = NSURL(string: "http://localhost/")
var array = [NSString]()
array.append((url?.absoluteString)!)
NSUserDefaults.standardUserDefaults().setValue(array, forKey: "image")
NSUserDefaults.standardUserDefaults().synchronize()
if let fetchedURL = NSUserDefaults.standardUserDefaults().valueForKey("image") as! [String]? {
print("\(fetchedURL[0]) returned")
let fileUrl = NSURL(string: fetchedURL[0])
print(fileUrl)
} else {
print("No URL in defaults")
}
print results
http://localhost/ returned
Optional(http://localhost/)

Related

Saving Firebase snapshot array to NSUserDefaults

I am using Swift to retrieve data from my Firebase database. When the user first logs in, I'd like to save the 'places' from my Firebase snapshot as a UserDefault.
static func getAllPlaces(){
databaseRef = Database.database().reference()
databaseRef.database.reference().child("places").observe(.childAdded) { (snapshot: DataSnapshot) in
if let value = snapshot.value as? NSDictionary {
let place = Place()
let id = value["id"] as? String ?? "ID not found"
let title = value["title"] as? String ?? "Title not found"
let type = value["type"] as? String ?? ""
place.id = id
place.title = title
place.type = type
DispatchQueue.global().async {
// Something here to append place data to UserDefaults?
places.append(place) // appends to NSObject for later use
}
}
}
}
The current code works fine - I just need to add something to get it stored so I can grab it later.
Bonus question: I am storing a good few hundred snapshots in the Firebase database. The reason I want to store them on the device is so that the user doesn't have to keep downloading the data. Is this a good idea? Would it take up a lot of memory?
Any help would be appreciated.
One way to save custom classes/data to UserDefaults is to encode them like this:
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: place)
UserDefaults.standard.set(encodedData, forKey: "place")
UserDefaults.standard.synchronize()
Then you can decode it like this:
if UserDefaults.standard.object(forKey: "place") != nil{
let decodedData = UserDefaults.standard.object(forKey: "place") as! Data
let decodedPlace = NSKeyedUnarchiver.unarchiveObject(with: decodedData) as! Place
}
Updated for swift 4 and iOS 12:
do {
let encodedData: Data = try NSKeyedArchiver.archivedData(withRootObject: place, requiringSecureCoding: false)
UserDefaults.standard.set(encodedData, forKey: "place")
UserDefaults.standard.synchronize()
} catch {
//Handle Error
}
do {
if UserDefaults.standard.object(forKey: "place") != nil{
let decodedData = UserDefaults.standard.object(forKey: "place") as! Data
if let decodedPlace = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decodedData) as? Place {
//Do Something with decodedPlace
}
}
}
catch {
//Handle Error
}

My catch isn't working on url request

I am trying to do my first try catch in Swift.
Essentially the user can give a name to something. So say he/she types in Gmail and hits submit.
Since G is the first letter of the String (doesn't matter if its lower or upper case) the image will load a picture of a G.
If the user also adds in a URL so say gmail.com or www.gmail.com it will pull the favicon for gmail.
So far so good.
HOWEVER. If the person types gmailllllll.com
it loads a picture of a globe (which I think is the default)
What I am trying to do is only put the gmail favicon in the image view if it is actually the image. If it is nil then I want to put the image of the G which I provided.
I understand why my code isn't working. I just do not know how to add to it to do what I want.
DispatchQueue.main.async {
let myURLString: String = "http://www.google.com/s2/favicons?domain=\(self.serviceArray[row].serviceUrl)"
let myURL = URL(string: myURLString)
do {
let myData = try Data(contentsOf: myURL!)
cell.serviceLogoImage.image = UIImage(data: myData)
} catch {
cell.serviceLogoImage.image = UIImage.init(named: "\(self.getLetterOrNumberAndChooseImage(text: self.serviceArray[row].serviceName))")
}
}
Also, If the user were to type in www.go google.com
with the space it has an optional crashe!
I tried saying
if myData == nil {...}
But it says it always returns false...
Any help would be appreciated
Optional binding
This solution avoids force unwrapping (ie myURL!) with optional binding.
Note that try? returns an optional. It returns nil rather than throwing an error. It is appropriate when you want to handle all errors in the same way.
let myURLString: String = "http://www.google.com/s2/favicons?domain=\(self.serviceArray[row].serviceUrl)"
if let myURL = URL(string: myURLString), let myData = try? Data(contentsOf: myURL), let image = UIImage(data: myData) {
cell.serviceLogoImage.image = image
}
else {
cell.serviceLogoImage.image = UIImage.init(named: "\(self.getLetterOrNumberAndChooseImage(text: self.serviceArray[row].serviceName))")
}
This expression Data(contentsOf: myURL!) is unsafe. I think you expect it to raise an error, but it will just crash your code (by design) if the URL cannot be parsed. Here is a safe example:
if let url = URL(string:mystring) {
do {
let data = try Data(contentsOf:url)
} catch {
print ("network issue")
}
} else {
print("bad string")
}
First of all, never EVER use ! unless you are sure that there is a value and not nil because forced unwrapping will crash your entire app. Second of all, I don't really get what are you trying to do with that Dispatch. I mean, if the user hits submit, you should make a function for example and you would call that when the submit button is tapped.
I would write something like this:
func updateImage() {
var myData: Data?
let myURLString: String = "http://www.google.com/s2/favicons?domain=\(self.serviceArra‌​y[row].serviceUrl)"
let myURL = URL(string: myURLString)
if let url = myURL {
do {
myData = try Data(contentfOf: url)
} catch {
print("error")
}
}
if let data = myData {
cell.serviceLogo.image = UIImage(data: data)
} else {
cell.serviceLogo.image = UIImage.init(named: "\(self.getLetterOrNumberAndChooseImage(text: self.serviceArray[row].serviceName))")
}
I really can't figure out what you were trying with that Dispatch, but eventually I'm thinking that first you should use a Dispatch.global(qos: .background).async where you would first verify what the user has entered and eventually download the necessary photos and then, when you're trying to update the UI, you should come back to the main thread with Dispatch.main.async. I guess.

Plist not changing value [swift]

I wanted to implement the in-app version for my app, but I don't understand why is not working? The output showing i was added successfully, but when i open the pList file from my Xcode, its not changing? Why is it? Here is my code:
func version(){
let urlString = jocomAPIKey + "/feed/version"
let CFBundleShortVersionKey = "CFBundleVersion"
//First get the nsObject by defining as an optional anyObject
let nsObject: AnyObject? = NSBundle.mainBundle().infoDictionary!["CFBundleVersion"]
let version = nsObject as! String
print("app version: \(version)")
Alamofire.request(.POST, urlString , parameters: ["os" : "iphone"])
.responseData { response in
let versionXML = SWXMLHash.parse(response.data!)
let versionString = (versionXML["rss"]["channel"]["item"]["version"].element?.text)
print("version string: \(versionString)")
if let plist = Plist(name: "Info") {
let dict = plist.getMutablePlistFile()!
dict[CFBundleShortVersionKey] = versionString
//3
do {
try plist.addValuesToPlistFile(dict)
print("added")
} catch {
print(error)
}
//4
print(plist.getValuesInPlistFile())
} else {
print("Unable to get Plist")
}
}
}
You can't update your (Bundle plist) at run-time. If you want use then you just make one copy on your Document Directory then you can perform any operation.
Write and Read a plist in swift with simple data
Apple not allow to update info.plist update at runtime in app.

Converting [NSURL] into [String] for NSUserDefaults?

I'm saving images into Parse Data as an array of NSURL's. Once I have them back into my app I would like to convert them to [String] so my app can temporarily store them. Any ideas?
Here is my code....
// Saving like This....
vc.videoImageArry = defaults.setObjectForKey("vidImages)
//Retrieving like This....
vc.vidImageArray = defaults.objectForKey("vidImages") as! [NSURL]
Using NSData
You can convert each NSURL to NSData in order to save it
func save(urls: [NSURL]) {
let urlsData = urls.map { $0.dataRepresentation }
NSUserDefaults.standardUserDefaults().setObject(urlsData, forKey: "urlsData")
}
Later on you can retrieve the NSData array and convert it back to [NSURL]
func load() -> [NSURL]? {
let retrievedData = NSUserDefaults.standardUserDefaults().arrayForKey("urlsData") as? [NSData]
return retrievedData?.map { NSURL(dataRepresentation: $0, relativeToURL: nil) }
}
Using String
Alternatively you can save the urls as String(s)
func save(urls: [NSURL]) {
let urlsData = urls.map { $0.absoluteString }
NSUserDefaults.standardUserDefaults().setObject(urlsData, forKey: "urlsData")
}
func load() -> [NSURL?]? {
let retrievedData = NSUserDefaults.standardUserDefaults().arrayForKey("urlsData") as? [String]
return retrievedData?.map { NSURL(string: $0) }
}
As discussed in the comments below, if data is written to NSUserDefaults exclusively with the save function, we know that every element of the array is a String representing a valid NSURL.
So we can change the return type of load from [NSURL?]? to [NSURL]? using this alternate version of load.
func load() -> [NSURL]? {
let retrievedData = NSUserDefaults.standardUserDefaults().arrayForKey("urlsData") as? [String]
return retrievedData?.flatMap { NSURL(string: $0) }
}
To convert from NSURL to String:
String(url)
To convert from String to NSURL:
NSURL(string: string)
Here's a fully working example that converts an array both ways:
import Cocoa
let urls = [NSURL(string: "http://www.swift.org/")!, NSURL(string: "http://www.apple.com/")!]
let strings = urls.map { String($0) }
let backToUrls = strings.map { NSURL(string: $0)! }
I believe that the above answers your specific question.
Having said that, the line for saving doesn't look right to me. You may want to look further into NSUserDefaults or ask a separate question if you're having difficulty with that line. You would need to paste some more context like lines above and below and exact error messages you're getting if any.

How to get the URL of a selected image when using UIImapgePickerController with Swift?

The following code crashes because 'imageURLString' is NIL which I kind of understand. But playing around with objectForKey did not bring me to Eden either. Could someone please give me a kick...?
Thnx!
PHImageManager.defaultManager().requestImageDataForAsset(item, options: nil){
imageData,dataUTI,orientation,info in
let imageURLString = info!["PHImageFileURLKey"] as! String
let imageURL = NSURL(string: imageURLString)
print("imageURL: \(imageURL)")
}
info!["PHImageFileURLKey"] is exact NSURL. If you force cast to String. It will crash here. See this below code for fix that.
PHImageManager.defaultManager().requestImageDataForAsset(item, options: nil){
imageData,dataUTI,orientation,info in
let imageURL = info!["PHImageFileURLKey"] as! NSURL
//let imageURL = NSURL(string: imageURLString)
print("imageURL: \(imageURL)")
}
or safe code:
PHImageManager.defaultManager().requestImageDataForAsset(item, options: nil){
imageData,dataUTI,orientation,info in
if let _ = info {
// info will not nil here
if let imageURL = info!["PHImageFileURLKey"] as NSURL {
print("imageURL: \(imageURL)")
}
}
//let imageURL = NSURL(string: imageURLString)
//print("imageURL: \(imageURL)")
}
Hope that helps!
PHImageFileURLKey directly returns NSURL and that is what your issue. You are assuming it to be String.