Swift 3 - set nested dictionary to UserDefaults - Crashes - dictionary

I am trying to save a nested dictionary into UserDefaults but whenever I try to save I get a crash:
Why is this crashing? In swift 2 it was working just fine
libc++abi.dylib: terminating with uncaught exception of type
NSException
this is my function:
var arrRes = [[String:AnyObject]]() //Array of dictionary
GetNewsFeed.getAllNews { (result) in
if let resData = result.arrayObject {
self.arrRes = resData as! [[String : AnyObject]]
self.defaults.set(self.arrRes, forKey: self.ARRAY_CACHE) // ERROR
self.defaults.synchronize()
print(self.defaults.dictionary(forKey: self.ARRAY_CACHE))
}
}

Very difficult to answer with just that snippet. I think the following line returns a nil value and causing the crash in the later line:
self.arrRes = resData as! [[String : AnyObject]]
Use following optional chaining technique and see if that solves :
if let data = resData as? [[String : AnyObject]] {
self.defaults.set(data, forKey: self.ARRAY_CACHE)
} else {
debugPrint("invalid data")
}

Related

Cannot reach to members of an array with index

I am trying to fill an array with data from Firebase. But after filling the array I can't call its' members from their index. I am filling the array by this function
func loadSounds(){
Firestore.firestore().collection("data").getDocuments{ (snapshot, error) in
if error == nil{
for document in snapshot!.documents{
let name = document.data()["name"] as? String ?? "error"
let sounds = document.data()["sounds"] as? [String : [String : Any]]
var soundsArray = [dataSound]()
if let sounds = sounds{
for sound in sounds {
let soundName = sound.value["name"] as? String ?? "error"
let soundImage = sound.value["image"] as? String ?? "error"
soundsArray.append(dataSound(name: soundName , image: soundImage ))
}
}
categoriesArray.append(Category(category: name , sounds: soundsArray))
}
print(categoriesArray[0].category)
} else {
print(error)
}
} }
When I try to access it from View, It gives index out of bounds error.
struct ContentView: View {
init(){
loadSounds()
}
var body: some View {
Text(categoriesArray[0].category)}}
If I try to access it via ForEach, it works, also when I try to print it from loadSounds function it works, but I need to access them from their index in View. Thanks for any help.
Never access items of an array by index in the rendering area of a SwiftUI View, in almost all cases the array is empty when the view is rendered the first time.
In your case use .first and handle the optional
var body: some View {
Text(categoriesArray.first?.category ?? "No value")}} // or empty string

perform network call and proceed - asynchronous task

i just started learning Swift a year ago, so please be patient with me :)
i am downloading JSON data with a network call, and as soon as i successfully received those rows, i then continue to clear the rows inside my coreData entity, and rewrite those new rows into coredata..
i am having a hard time understanding this asynchronous procedure..
what i've learned is that i have to use completion handlers, but i still can't use it the way i need to.. especialy when i need to proceed after those 3 steps were executed..
First call from button action:
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
textField.text = "blabla" // gets executed before the result is available
methods:
func update(completion: #escaping (String?) -> Void) { //parent method which calls sub methods
var returnValue = ""
Step1getJson {_ in. // step 1
self.Step2Delete { // step 2
self.Step3Save { // step 3
returnValue = "return Value: \(self.step1Result)"
completion(returnValue)
}
}
}
}
func Step1getJson(completion: #escaping (Bool) -> ()) {
var success = false
if let url = URL(string: "https:foo") {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
do {
let parsedJSON = try JSONDecoder().decode([RemoteWire].self, from: data)
print("-- Successfully received \(parsedJSON.count) datarows ")
self.JSON = parsedJSON
self.step1Result = "-- Successfully received \(parsedJSON.count) datarows "
success = true
} catch {
print(error)
}
completion(success)
}.resume()
}
}
func Step2Delete(completion: () -> Void) {
...delete entity rows
completion()
}
func Step3Save(completion: () -> Void) {
.. save new JSON rows to coreData
completion()
}
Everything is working fine that far, and step 2 and step 3 get successfully called when network download has finished..
but how can i proceed after those steps were executed inside my updateButtonPressed function?
if i try to write those results into any UI element inside my completion block, a textField or whatever, i get an error that this has to happen in the main thread, and if i execute it outside the completion block those lines get executed far too early, when no results are available yet.
i feel like i have understanding problem with this, i hope you guys can help me out and guide me in the right direction.
As swift allows any changes or updates in UI element only from main thread, you need to call the main thread to update the UI.
Replace the below code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
}
with the new code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
DispatchQueue.main.async {
textField.text = success! // Now possible because it is in main thread
}
}
}

SwiftUI Firebase - How to query a document then update?

Trying to query a document and then update it in a function in my ViewModel. Trying something like the below, but it doesn't work. Any advice would be appreciated!
func addToFruits(name: String) {
db.collection("fruits").whereField("name", isEqualTo: name)
.getDocument()
.limit(to: 1)
.updateData(["fruits": FieldValue.arrayUnion([name])])
}
func addToRoutine(routine: String, habit: String) {
db.collection("routines").whereField("name", isEqualTo: routine).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
document.updateData(["habits": FieldValue.arrayUnion([habit])])
}
}
}
}
In the first one, error I get is "Value of type 'Query' has no member 'getDocument'" and not sure how to resolve this. Second one, error I get is "Value of type 'QueryDocumentSnapshot' has no member 'updateData'"
It's not exactly clear what you're attempting to update but here's some quick example code that will read in a user named 'Steve' and update his age to 50. Keep in mind this will read in the FIRST user named 'Steve' and update their age - then when it's run again, will read the NEXT Steve etc etc - that may be what your attempting to do.
func readUsersAndUpdateTheirAgeTo50() {
let users = self.db.collection("users") //self.db points to *my* firestore
users.whereField("name", isEqualTo: "Steve").limit(to: 1).getDocuments(completion: { querySnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = querySnapshot?.documents else { return }
for doc in docs {
let docId = doc.documentID
let name = doc.get("name")
print(docId, name)
let ref = doc.reference
ref.updateData(["age": 20])
}
})
}
If I just wanted to update all Steve's age to 50, remove the limit
.limit(to: 1)
Note this code is kind of sloppy as since there is a limit of 1, we wouldn't need the loop. Also note that not every Steve is 50 so there should be additional parameters to narrow down which Steve it is - like a uid for example.

Could not cast value of type '__NSDictionaryM' (0x1111152b0) to 'FIRDataSnapshot' Firebase Swift 3

I am trying to read nested data structures from Firebase Database, but I don't know how to manage the case when an object of type [String:AnyObject] could be nil.
When readFeesCleaner(callback_) is called, it throws an error.
func readFeesCleaner(callback: #escaping ((_ feesCleaner: FeesCleaner) -> Void)) {
dbRef.child("FeesCleaner").child(self.uidOfTextField!).observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
guard !(snapshot.value is NSNull) else {
return
}
//throws error: signal SIGABRTCould not cast value of type '__NSDictionaryM' (0x1111152b0) to 'FIRDataSnapshot' (0x10ef16d18).
let feesCleanersReceived = FeesCleaner(snapshot: (snapshot.value)! as! FIRDataSnapshot)
callback(feesCleanersReceived)
}) { (error:Error) in
print(#line, "\(error.localizedDescription)")
}
}
struct FeesCleaner {
var outstandingFees: AnyObject!
var timeStampFeesSaved: [String:AnyObject]!
var backgroundCheck: AnyObject!
init(
outstandingFees: AnyObject? = nil, //value might not exist when reading
timeStampFeesSaved: [String:AnyObject]? = nil,// value might not exist when reading
backgroundCheck: AnyObject) {
self.outstandingFees = outstandingFees
self.timeStampFeesSaved = timeStampFeesSaved
self.backgroundCheck = backgroundCheck
}//end of init
//read data here
[full struct data here][1]
https://gist.github.com/bibscy/dc48f7107459379e045a50fdbbc35335
}//end of struct
There's a number of issues here. First:
how to manage the case when an object of type [String:AnyObject]
could be nil.
You've handled that with the prior statement, noting that you can also add
if snapshot.exists == false {return}
Second: You've got to handle optionals properly - if a var could be nil, you need code in place to handle that situation and not plow through it. If you force unwrap an optional you are essentially stating that for sure, it will never be nil, so basically, don't do that.
One fix could be to simply pass the snapshot as a DataSnapshot and then pull out the properties one at a time; if they exist, assign them, if not set to 0 or nil or some other placeholder.
Something like this inside the Firebase closure:
let feesCleanersReceived = FeesCleaner(withSnapshot: snapshot)
and then your struct like this: note we are leveraging the nil coalescing operator, ??
struct FeesCleanerStruct {
var outstandingFees: String?
var timeStampFeesSaved: String?
init(withSnapshot: DataSnapshot) {
let dict = withSnapshot.value as! [String: Any]
self.outstandingFees = dict["outstandingFees"] as? String ?? "0.0"
self.timeStampFeesSaved = dict["timeStampFeesSaved"] as? String ?? "0"
}
}

Swift 3.0 conversion - contains(_:) in Collection

I am trying to convert a project from Swift 2.3 to Swift 3.
Here is some issue with contains(_:) from Collection:
extension Collection {
subscript (safe index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
The error is Missing argument label 'where:' in call
I added where:, but now, another error appears:
Cannot convert value of type 'Self.Index' to expected argument type '(_) throws -> Bool'
From Swift 3.0 language guide it seems that it should work without errors:
if favoriteGenres.contains("Funk") {
print("I get up on the good foot.")
} else {
print("It's too funky in here.")
}
In Swift 3, the indices property of Collection is not a Collection, but just an IndexableBase & Sequence. Which has no contains(_:) method, but only contains(where:) method.
(From the generated header.)
associatedtype Indices : IndexableBase, Sequence = DefaultIndices<Self>
public var indices: Self.Indices { get }
You may need to write something like this:
extension Collection {
subscript (safe index: Index) -> Iterator.Element? {
return (startIndex..<endIndex).contains(index) ? self[index] : nil
}
}
Or else you can invoke contains(_:) method for Sequence where Iterator.Element : Equatable, with adding some constraints:
extension Collection
where Indices.Iterator.Element: Equatable, Index == Indices.Iterator.Element
{
subscript (safe index: Indices.Iterator.Element) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}
Both work for simple Arrays:
let arr = [1,2,3]
print(arr[safe: 3]) //->nil
print(arr[safe: 2]) //->Optional(3)
But I'm not sure which is the safer generally.

Resources