I want to be able to save an array, cardImages, that contains UIImages via SwiftyUserDefaults.
Desired Behavior
Here is the exact desired behavior:
Save an array of UIImages to NSUserDefaults via the SwiftyUserDefault library
Retrieve the images later
Code This is stripped down to very little code
var newPhotoKey = DefaultsKey<NSArray>("image")//Setting up the SwiftyUserDefaults Persisted Array
cardImages = [(UIImage(named: "MyImageName.jpg")!)] //This array contains the default value, and will fill up with more
Defaults[theKeyForStoringThisArray] = cardImages //This is the persisted array in which the array full of images should be stored. WHERE THE ERROR HAPPENS
var arrayToRetreiveWith = Defaults[theKeyForStoringThisArray] as! [UIImage] //To Retreive
Error
I get the following error:
Attempt to set a non-property-list object (
", {300, 300}"
) as an NSUserDefaults/CFPreferences value for key image
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object (
", {300, 300}"
) for key image'
Thanks!
The error message is clear actually. UIImage is not a propertylist so you need to change it to a row data first. I'll put example below but FYI saving big data like images using NSUserDefaults is really not recommended. I'd use NSFileManager and put it in the user documents directory. anyway
var newPhotoKey = DefaultsKey<NSArray>("image")
cardImages = [(UIImage(named: "MyImageName.jpg")!)]
var cardImagesRowdataArray: NSData = []
for image in cardImages {
let imageData = UIImageJPEGRepresentation(image, 1.0)
cardImagesRowdataArray.append(imageData)
}
Defaults[theKeyForStoringThisArray] = cardImagesRowdataArray
var arrayToRetreiveWith = Defaults[theKeyForStoringThisArray] as! [NSData]
// here you can use UIImage(data: data) to get it back
If you don't insist on using SwiftyUserDefaults, you can save it in the user documents directory, here is how to do it
func saveImage(image: UIImage){
if let imageData = UIImageJPEGRepresentation(image, 1.0) {
let manager = NSFileManager()
if let docUrl = manager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first{
let uniqueName = NSDate.timeIntervalSinceReferenceDate()
let url = docUrl.URLByAppendingPathComponent("\(uniqueName).jpg")
imageData.writeToURL(url, atomically: true)
}
}
}
The value of a user default must be a property list. A property list is
a string (String or NSString),
an NSData,
a date (NSDate),
a number (NSNumber),
a boolean (also NSNumber),
an array of property lists,
or a dictionary whose keys are strings and whose values are property lists.
A UIImage is none of those, so a UIImage is not a property list and cannot be part of a property list.
You need to convert your image to an NSData to store it in a user default. Since a UIImage contains some properties (like scale and imageOrientation) in addition to raw pixel data, the easiest way to convert a UIImage to an NSData with no loss is by creating an archive:
let cardImage: UIImage? = someImage()
let cardImageArchive: NSData = NSKeyedArchiver.archivedDataWithRootObject(cardImage!)
Now you can store cardImageArchive in a larger property list that you can store as a user default.
Later, when you need to recreate the image from the data, do this:
let cardImageArchive: NSData = dataFromUserDefaults()
let cardImage: UIImage = NSKeyedUnarchiver.unarchiveObjectWithData(cardImageArchive) as! UIImage
Related
I have an array of dictionaries that is being read in from a JSON file as seen below. I would like to store that value (jsonResult) into a class variable so that I can use it to populate a tableview. However, I don't quite understand how to store that value.
Here is how I am getting my array of dictionaries (jsonResult):
if let path = Bundle.main.path(forResource: filename, ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as! [String:Any]
self.tableData = jsonResult // WHAT GOES HERE?
} catch {
// handle error
}
}
And this is my class variable that I want to store my array of dictionaries into:
var tableData = [Dictionary<String, String>]()
How can I correctly store jsonResult into tableData? I do not want to use a struct as the structure of the dictionaries can vary.
You state the JSON is an array of dictionary but you are casting the result of JSONSerialization.jsonObject to just a dictionary. Since you seem to be expected an array of dictionary with both string keys and values, cast the result accordingly. But do it safely. Never use ! when working with JSON.
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [[String:String]] {
self.tableData = jsonResult
} else {
// Error - unexpected JSON result
}
This assumes you want the top level of the JSON result. If in fact jsonResult should be a dictionary and that top-level dictionary has a key to the actual array of dictionary you want then you need to fix the code accordingly.
I've got a server response returning
(
{
agreementId = "token.virtual.4321";
city = AMSTERDAM;
displayCommonName = "bunch-of-alphanumeric";
displaySoftwareVersion = "qb2/ene/2.7.14";
houseNumber = 22;
postalCode = zip;
street = "";
}
)
how do I get the value of agreementId? response['agreementId'] is not working. i've tried some example code with .first but I cannot get it working.
Some extra information, I do a http call to a server with alamofire. I try to parse the json to a constant response:
let response = JSON as! NSDictionary
However that returns a error message
Could not cast value of type '__NSSingleObjectArrayI' (0x1083600) to 'NSDictionary' (0x108386c).
So now parse the json to an array, which seems to be working. The code above is what
let response = JSON as! NSArry
print(response)
spits out.
Now I only need to retrieve the value from the key "agreementId" and I have no clue how to do that.
In swift you need to use Swift's native type Array/[] and Dictionary/[:] instead of NSArray and NSDictionary, if you specify the type like above means more specific then the compiler won't complain. Also use optional wrapping with if let or guard let to prevent crash.
if let array = JSON as? [[String:Any]] {//Swift type array of dictionary
if let dic = array.first {
let agreementId = dic["agreementId"] as? String ?? "N/A"//Set default value instead N/A
print(agreementId)
//access the other key-value same way
}
}
Note: If you having more than one object in your array then you need to simply loop through the array to access each dictionary of array.
if let array = JSON as? [[String:Any]] {//Swift type array of dictionary
for dic in array {
let agreementId = dic["agreementId"] as? String ?? "N/A"//Set default value instead N/A
print(agreementId)
//access the other key-value same way
}
}
I am currently struggling with obtaining a value from an array inside an array of dictionaries. Basically I want to grab the first "[0]" from an array stored inside an array of dictionaries. This is basically what I have:
var array = [[String:Any]]()
var hobbies:[String] = []
var dict = [String:Any]()
viewDidLoad Code:
dict["Name"] = "Andreas"
hobbies.append("Football", "Programming")
dict["Hobbies"] = hobbies
array.append(dict)
/// - However, I can only display the name, with the following code:
var name = array[0]["Name"] as! String
But I want to be able to display the first value in the array stored with the name, as well. How is this possible?
And yes; I know there's other options for this approach, but these values are coming from Firebase (child paths) - but I just need to find a way to display the array inside the array of dictionaries.
Thanks in advance.
If you know "Hobbies" is a valid key and its dictionary value is an array of String, then you can directly access the first item in that array with:
let hobby = (array[0]["Hobbies"] as! [String])[0]
but this will crash if "Hobbies" isn't a valid key or if the value isn't [String].
A safer way to access the array would be:
if let hobbies = array[0]["Hobbies"] as? [String] {
print(hobbies[0])
}
If you use a model class/struct things get easier
Given this model struct
struct Person {
let name: String
var hobbies: [String]
}
And this dictionary
var persons = [String:Person]()
This is how you put a person into the dictionary
let andreas = Person(name: "Andreas", hobbies: ["Football", "Programming"])
persons[andreas.name] = Andreas
And this is how you do retrieve it
let aPerson = persons["Andreas"]
Well I have been making a test app to continue my swift learning, today I came across a problem.
Basically I have a tableview and a detailview. For my tableview file I have some data that I am currently passing to the detailview, like the name that goes on the navigation bar, one image and some text, this data is stored in arrays on my tableview file, I use "prepareforsegue" to pass this information:
var names = ["name1","name2","name3"]
detailViewController.detailName = names[indexPath.row]
Then in my detailViewController I have variables set for that:
var detailName: String?
Then I use this for stuff, example: naming my navigation bar or setting an image in detailView:
navigationItem.title = detailName!
Now, what I dont get how to do is pass a whole array of information to a variable in my detailViewController. What I want to do is pass an array of images to use it on my detailView. I want to be able to iterate through the array of images with a button, I know how to set that up but I just need to know how to pass the array, right now I am just passing one of the values(one name, one image etc...)
Thanks for the help in advance.
It's not so different from what you have done.
Just add field for images in detailViewController, and then pass images using it. Images could be represented in [UIImage]. However, [String] can be also used for local filenames, and [NSURL] can be used for remote image urls.
code:
In DetailViewController:
var images: [UIImage]? // or var images: [UIImage] = []
In prepareForSegue:
detailViewController.images = YourImages
You seem to be asking for one of two things:
A UIImage array, which you can declare using var imageArray : [UIImage] = [] and then append whatever images you want to pass.
Alternatively, you can pass in a [AnyObject] array and cast its elements to a UIImage or String by doing
var objectArray : [AnyObject] = []
objectArray.append("test")
objectArray.append(UIImage(named: "test.png")!)
if let s = objectArray[0] as? String {
// Do something with the string
}
if let i = objectArray[1] as? UIImage {
// Do something with the image
}
if let s = objectArray[1] as? String {
// The above cast fails, this code block won't be executed
} else {
// ... but this one will
}
in the table view controller file:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get reference to the destination view controller
var detailVC = segue.destinationViewController as! DetailViewController
var detailImages: Array<UIImage> = []
detailImages.append(UIImage(named: "pup.png")!)
detailImages.append(UIImage(named: "cutepuppy.png")!)
detailVC.detailImages = detailImages;
}
and in the detail file:
var detailImages: Array<UIImage>?
I want to store a dictionary as:
[String : AnyItem]
because the format will be
["codename" : "title", "image" : UIImage("imageName")]
but I am having a difficult type accomplishing this. The ultimate goal is to have an array full of these types of dictionaries.
If you want a dictionary value that can hold anything, you need Any (rather than AnyType.
But this will be a real pain – whenever you want to get the value out, you’ll have to covert the types back every time, and if you mess up doing that, you’ll get all sorts of errors.
Instead, consider using a tuple – a pair of the two types you want. You can name the elements to make them easier to access:
var images: [(codename: String, image: UIImage)] = []
if let url = NSURL(string: "http://www.gravatar.com/avatar/24d29e1ff91b68642bbef96b43b5e119?s=128&d=identicon&r=PG&f=1"),
let data = NSData(contentsOfURL: url),
let image = UIImage(data: data) {
images.append(codename: "AlexAvatar", image: image)
}
images[0].codename // name of the first image in the array
images[0].image // the first image in the array
Alternatively, if you are going to want to address the images by name, why not use the “codename” field as the dictionary key?
var images: [String:UIImage)] = []
if let url = NSURL(string: "http://www.gravatar.com/avatar/24d29e1ff91b68642bbef96b43b5e119?s=128&d=identicon&r=PG&f=1"),
let data = NSData(contentsOfURL: url),
let image = UIImage(data: data) {
images["AlexAvatar"] = image
}
for (codename, image) in images {
// iterate over the codename/image pairs
}
If your actual data requirements are more complicated, consider a struct instead of a tuple.