I have an array with type [NotificationTriggers] that I would like to store in userdefaults. To do that, the data needs to be encoded and decoded. I have followed tutorials here:
https://cocoacasts.com/ud-5-how-to-store-a-custom-object-in-user-defaults-in-swift
and here:
https://www.hackingwithswift.com/example-code/system/how-to-load-and-save-a-struct-in-userdefaults-using-codable
But I still get an error that I can't seem to solve.
I have an extension of userDefaults where I do the magic in the get and set of the variable. NotificationTriggers Struct looks like this:
struct NotificationTriggers: Equatable, Codable {
var doorName: String
var notificationTrigger: String
}
Encoding seems to work, but in decoding I get an error saying
Cannot convert value of type '[Any]' to expected argument type 'Data'
This is the code:
extension UserDefaults {
var notificationTrigger: [NotificationTriggers] {
get {
if let data = self.array(forKey: UserDefaultsKey.notificationTrigger.rawValue) {
do {
let decoder = JSONDecoder()
//CODE BELOW PRODUCE ERROR
if let decodedData = try decoder.decode([NotificationTriggers]?.self, from: data) {
return decodedData
}
} catch { }
}
return []
}
set {
do {
let encoder = JSONEncoder()
let data = try encoder.encode(newValue)
self.setValue(data, forKey: UserDefaultsKey.notificationTrigger.rawValue)
} catch { }
}
}
}
I have tried casting the data:
UserDefaultsKey.notificationTrigger.rawValue) as? Data // get warning "Cast from '[Any]?' to unrelated type 'Data' always fails"
UserDefaultsKey.notificationTrigger.rawValue) as? [NotificationTriggers] // get error "Cannot convert value of type '[NotificationTriggers]' to expected argument type 'Data'"
Not sure what's missing here. Any ideas?
You save Data for the key UserDefaultsKey.notificationTrigger.rawValue with:
let encoder = JSONEncoder()
let data = try encoder.encode(newValue)
self.setValue(data, forKey: UserDefaultsKey.notificationTrigger.rawValue)
So the first mistake I see:
if let data = self.array(forKey: UserDefaultsKey.notificationTrigger.rawValue) {
array(forKey:)? No, data(forKey:), you didn't save an Array, you saved a Data, a Data that might after some decoding "hides" an Array, but the system doesn't know it.
So, it should be:
if let data = self.data(forKey: UserDefaultsKey.notificationTrigger.rawValue) {
Then:
let decodedData = try decoder.decode([NotificationTriggers]?.self, from: data)
=>
let decodedData = try decoder.decode([NotificationTriggers].self, from: data)
Also, it's bad habit to have catch { }, if there is an error, you might want to know it:
catch {
print("Error while doingSomethingToCustomizeHere: \(error)")
}
Related
I already have the response data that I received from the server. This response data have some bakers data.
Now I want to calculate the distance of the user and bakery and then store it in the same modal class. I have created a function for it. And as this function need to be used in 4,5 view controllers, my plan is to create as an extension of UIViewController
func getDistanceUserBakery(bakeryData : inout [BakeryRecord], completion : #escaping (Int?) -> () ) {
for index in 0...(bakeryData.count-1) {
//1
let googleApiAdd = "https://maps.googleapis.com/maps/api/distancematrix/json?units=imperial&"
//2
let origin = "origins=\(UserLocation.coordinates.latitude),\(UserLocation.coordinates.longitude)"
//3
let destination = "&destinations=\(bakeryData[index].location?.coordinates?[1] ?? 0.0),\(bakeryData[index].location?.coordinates?[0] ?? 0.0)"
//4
let googleKey = "&key=\(GOOGLE_KEY)"
//5
let url = googleApiAdd + origin + destination + googleKey
let request = URLRequest(url: URL(string: url)!)
//6 - this line is showing the error.
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
guard let data = data else {
completion(nil)
Toast.show(message: "Unable to calculate distance from user to bakery", controller: self)
return }
let stringResponse = String(data: data, encoding: .utf8)!
let dictData = stringResponse.convertToDictionary()
do {
let jsonData = try JSONSerialization.data(withJSONObject: dictData as Any, options: .prettyPrinted)
let decoder = JSONDecoder()
let model = try decoder.decode(GoogleDistance.self, from: jsonData)
bakeryData[index].disanceInMiles = model.rows?[0].elements?[0].distance?.text ?? "NaN"
completion(index)
} catch let parsingError {
print("Error data :", parsingError)
completion(nil)
}
}
task.resume()
}
This is how I call this function once I have received the data from my server,
self.getDistanceUserBakery(bakeryData: &self.bakeryData) { index in
if index != nil {
DispatchQueue.main.async {
// here I am thinking as the bakeryData will hold the new value for distanceInMiles, the collectionView will start showing up that result on reload.
self.resultCollection.reloadItems(at: [IndexPath(item: index!, section: 0)])
}
}
}
Now the Question:
As I know, when you pass parameters as inout, there values can be changed from inside your function, and those changes reflect in the original value outside the function.
But when I try the code , it says Escaping closure captures 'inout' parameter 'bakeryData'. In my code , //6 is producing the error.
How to fix this error?
As #Paulw11 suggested in comments,
Is BakeryData a struct? If so then simply make it a class. If you make
BakerData a class then the array contains reference types and you can
update the element's properties
I changed the struct to class and it did work.
I am trying to read data from Firebase Firestore, but there is no way to store the document data in a variable, because those variables will just be local and will be "garbage collected"
I tried making a function that tries to return the document content, but that resulted in an error because the "getDocument" method does not allow a return type.
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
var dataValues = document.data()!.values
self.textInput = dataDescription
//I first tried doing it without the word self, but it gave an error insisting that I add self, but adding it makes it a local variable and not the String variable defined in the class
//textInput is a String
print("Document data: \(document.data()!.values)")
//This will print out everything in the document
} else {
print("Document does not exist")
}
}
I am trying to see if I can set any variable to the document data and still be able to access the data after reading the database.
I will suggest you to take look to https://firebase.google.com/docs/firestore/manage-data/transactions , Firebase have one of the most complete documentation, this will help you, to be exact you should do it like this:
let sfReference = db.collection("cities").document("SF")
db.runTransaction({ (transaction, errorPointer) -> Any? in
let sfDocument: DocumentSnapshot
do {
try sfDocument = transaction.getDocument(sfReference)
} catch let fetchError as NSError {
errorPointer?.pointee = fetchError
return nil
}
guard let oldPopulation = sfDocument.data()?["population"] as? Int else {
let error = NSError(
domain: "AppErrorDomain",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)"
]
)
errorPointer?.pointee = error
return nil
}
// Note: this could be done without a transaction
// by updating the population using FieldValue.increment()
transaction.updateData(["population": oldPopulation + 1], forDocument: sfReference)
return nil
}) { (object, error) in
if let error = error {
print("Transaction failed: \(error)")
} else {
print("Transaction successfully committed!")
}
}
as described in the Firebase Documentation.
I have a JSON file and I'm trying to access the array in it.
The JSON file looks like:
{
"cars": [{
"name": "BMW",
"icons": [["front.png", "back.png", "B3"],
["front_red", "back_red", "C4"]
]
}]
}
//cars is an array of dictionaries, I just mentioned one in the snippet.
I get the JSON data as:
func loadJSONData(){
if let path = Bundle.main.path(forResource: "testJSON", ofType: "json")
{
if let jsonData = NSData(contentsOfFile : path)
{
do {
if let jsonResult = try JSONSerialization.jsonObject(with: jsonData as Data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String:Any]
{
self.testJSONData = (jsonResult["cars"] as? Array)!
//self.testJSONData = (jsonResult["cars"] as? Array<Dictionary<String, Any>>)! //also tried this
}
}
catch let error as NSError {
print(error.localizedDescription)
}
}
}
}
testJSONData is declared as an array:
var testJSONData = [] as [Dictionary<String, Any>]
and the error occurs when trying to get the "icons" array from the JSON.
let namePredicate = NSPredicate(format: "name like BMW")
let filteredArray :Array = testJSONData.filter() { namePredicate.evaluate(with: $0) }
let carData: Dictionary = filteredArray[0] as Dictionary<String, Any>
let carIcons: Array = carData["icons"] as! Array //error at this line
Cannot convert value of type 'Array<_>' to specified type 'Array'
Can someone please show me where I'm doing wrong ? Thanks!
Array is a generic type in Swift, so when you want to declare an array variable, you always need to specific what type of elements the Array is holding. There's no such type as Array without specifying its Element type.
Also, there's no need for type annotations in Swift, the compiler can infer the types for you and you are explicitly telling the compiler the type by casting anyways.
carIcons should be of type Array<Array<String>> or as a shorthand [[String]]
let carIcons = carData["icons"] as! [[String]]
Some general comments about your code: don't use old Foundation types, such as NSData in Swift when they have native Swift equivalents. Also don't do force unwrapping of safe casted types, that makes no sense. Either handle the casting and unwrapping safely or simply force cast if you know the cast will succeed for sure. .mutableContainers have no effect in Swift, so don't use it. There's no need to cast error to NSError in a catch block, Swift has its own Error type.
func loadJSONData(){
if let fileURL = Bundle.main.url(forResource: "testJSON", withExtension: "json") {
do {
let jsonData = try Data(contentsOfFile: fileURL)
if let jsonResult = try JSONSerialization.jsonObject(with: jsonData) as? [String:Any], let cars = jsonResult["cars"] as? [[String:Any]] {
self.testJSONData = cars
} else {
print("Unexpected JSON format")
}
}
catch {
print(error)
}
}
}
However, if you are using Swift 4, the best solution would be using the type safe Codable protocol and JSONDecoder instead of JSONSerialization.
I'm working on a Cocktailapp and want to save my image data to Core Data.
I watched some tutorials and did the same but it's still not working an I don't know why.
I have an Array with all titles for the images:
let imagesAperitif: [String] = ["americano.jpg", "daiquiri.jpg",
"frozen daiquiri.jpg",
"banana frozen daiquiri.jpg", "bronx.jpg", "kir.jpg",
"hugo.jpg", "Manhattann.jpg", "manhattan dry.jpg", "manhattan medium.jpg", "margarita.jpg",
"martini dry.jpg",...
Thats where I call my method for saving the images to Core Data:
setCocktails(nameInsert, zutaten: zutatenInsert, zubereitung: zubereitungInsert, dekoration: dekorationInsert, stil: stilInsert, bild: UIImage(named: imagesAperitif[index])!)
That's a part from the code of saveCocktails method:
let imageData = NSData(data: UIImageJPEGRepresentation(bild, 1.0)!)
eintrag.setValue(imageData, forKey: "bild")
do {
try managedContext.save()
That's part of the fetching method:
let fetchRequest = NSFetchRequest(entityName: "Cocktail")
do {
let results =
try managedContext.executeFetchRequest(fetchRequest)
cocktails = results as! [NSManagedObject]
And here I want to get my image back from NSData:
imagesAperitif.append(UIImage(data: eintrag.valueForKey("bild") as! NSData)!)
But the App crashes with this line and I get a "fatal error: unexpectedly found nil while unwrapping an Optional value" error.
Is anybody able to explain this to me because I don't know what to change. Everything I tried went also wrong.
You may want to check nil for eintrag.valueForKey("bild") before case it to NSData
As
func valueForKey(_ key: String) -> AnyObject?
And it's always save to check nil before you append UIImage(data: eintrag.valueForKey("bild") as! NSData)
(I like using guard so here goes :) )
So what I would do here since it looks like the inserting of the NSData object fails:
// We are simultaniously unwrapping objects and checking if objects are nil.
guard let imgAsNSData: NSData = UIImageJPEGRepresentation(bild, 1.0),
let entity: Cocktail = eintrag as? Cocktail else {
// Stop executing the method here, there is no point in going further. Handle any errors here! Either imgAsNSData is nil, or could not cast to your class. A guard statement handles it's errors here.
return
}
// At this point we know we have an NSData object. Assign it to the entity.
entity.bild = imgAsNSData
do {
// Save our entity.
try managedContext.save()
} catch {
// Handle error
}
Fetching:
let fetchRequest = NSFetchRequest(entityName: "Cocktail")
do {
let results: [NSManagedObject] = try managedContext.executeFetchRequest(fetchRequest)
guard let cocktails = results as? [Cocktail] else {
// Could not cast to an array of Cocktail objects.
return
}
// Do stuff with the cocktails object.
Add to your array:
// 1: Check if entity not is nil
// 2: Check if entity's bild property not is nil.
// 3: Check if we can create an image using the NSData
guard let cocktail: Cocktail = eintrag as? Cocktail,
let imgAsNSData: NSData = cocktail.bild,
let image: UIImage = UIImage(data: imgAsNSData) else {
// Required values are nil. Cannot proceed.
return
}
imagesAperitif.append(image)
Untested code so be careful :)
I've started a Swift 2 project and I'm attempting to wrap my head around do, guard and throws. There are several other questions on Stack concerning this, but the issue I'm having is slightly different.
Here is my code:
enum JSONParsingError: String, ErrorType {
case URLCreationFailed = "Error: URL creation failed"
case SerializationFailed = "Error: JSON Parsing failed"
case DataDownloadingFailed = "Error: downloading data failed"
case DictionaryError = "Error: dictionary creation from JSON failed."
}
func fetchUserRepositories(urlString: String) throws {
do {
guard let reposURL = NSURL(string: urlString) else { throw JSONParsingError.URLCreationFailed }
guard let jsonData = NSData(contentsOfURL: reposURL) else { throw JSONParsingError.DataDownloadingFailed }
guard let jsonDictionary: NSDictionary = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .MutableContainers) as? NSDictionary else { throw JSONParsingError.SerializationFailed }
guard let reposArray = jsonDictionary["repos"] as? NSDictionary else { throw JSONParsingError.DictionaryError }
for repo in reposArray {
repositories.append(TestRepo(json: repo))
}
}
}
Regardless of how I cast jsonDictionary["repos"] I keep getting the same error in my for loop:
Cannot convert value of type 'Element' (aka '(key: AnyObject, value: AnyObject)') to expected argument type 'NSDictionary' (TestRepo is just a simple class that is initialized with a dictionary. Not the most ideal way, I know).
What am I missing?
reposArray is a dictionary, that is a collection of couples (key, value). In your code:
for repo in reposArray {
repositories.append(TestRepo(json: repo))
}
repo is bound to each element in turn. So, if TestRepo expects as parameter a dictionary, it receives instead an element (a couple (key, value)), and this is the cause of the error.