Save NSMutableArray with Class to Plist File in Swift - ios

Hey I just want to save my NSMutableArray with Class. But When i try to read the file my app getting crush. I found this and i tried to convert for NSMutableArray. Now I cant figure out what can i do.
My Class:
class Customer {
var name = String()
var email = String()
var phoneNumber = Int()
var bAdd = String()
var bAdd2 = String()
var sAdd = String()
var sAdd2 = String()
init(name: String, email: String, phoneNumber: Int, bAdd: String, bAdd2: String, sAdd: String, sAdd2: String) {
self.name = name
self.email = email
self.phoneNumber = phoneNumber
self.bAdd = bAdd
self.bAdd2 = bAdd2
self.sAdd = sAdd
self.sAdd2 = sAdd2
}
class func exists (path: String) -> Bool {
return NSFileManager().fileExistsAtPath(path)
}
class func read (path: String) -> NSMutableArray? {
if Customer.exists(path) {
return NSMutableArray(contentsOfFile: path)!
}
return nil
}
class func write (path: String, content: NSMutableArray) -> Bool {
return content.writeToFile(path, atomically: true)
}
}
My Array:
var ItemData:NSMutableArray = NSMutableArray()
and My Read Code:
let documents = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let customerPath = documents.stringByAppendingPathComponent("Test.plist")
ItemData = Customer.read(customerPath)!
When i tried to read im gettin this crash:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Any Advice?

Your code works perfectly fine except that you do not actually have anything to read from in the beginning, therefore Customer.read(customerPath) returns nil, which you try to unwrap - therefore the error.
If you just write something beforehand and then try to read it again like this, everything works fine.
ItemData.addObject("aisnd")
Customer.write(customerPath, content: ItemData)
ItemData = Customer.read(customerPath)!
Of course that is not the way to actually do it, because it is normal that you don't have anything present in the beginning. Therefore you have to check wether or not the read function actually returns something useful:
var ItemData:NSMutableArray = NSMutableArray()
if let item = Customer.read(customerPath) {
ItemData = item
} else {
print("no value found")
}
Final note 1 because playground suggested it: remove the ! from the as! String. Final note 2: don't start variable names uppercase, the variable should be called itemData.
Edit
To write Customer objects you have to do something like this.

Related

Failable Init - Variable 'self.customerType' used before being initialized

I've read up and down regarding this, and understand the basics here - I just can't understand why I get this error. I use the second init to instantiate a customer from Firebase, but even if I comment out everything inside it, I still get the error
Variable 'self.customerType' used before being initialized at the declaration of init?
class Customer {
enum customerTypes {
case consumer
case business
}
// MARK - properties
var id: String?
let customerType: customerTypes
var name1: String
var name2: String?
var address: Address?
var phone: String?
var email: String?
struct Address {
var street: String
var postalCode: String
var city: String
var country: String = "Norway"
}
init(type: customerTypes, name1: String, name2: String?, phone: String, email: String, address: Address? ) {
self.customerType = type
self.name1 = name1
self.name2 = name2
self.phone = phone
self.email = email
self.address = address
}
init?(data: [String: Any]) {
guard let type = data["customerType"] as? String else { return }
guard let name1 = data["name1"] as? String else { return }
self.customerType = type == "Consumer" ? .consumer : .business
self.name1 = name1
// if let name2 = data["name2"] as? String { self.name2 = name2 }
// if let phone = data["phone"] as? String { self.phone = phone }
// if let email = data["email"] as? String{ self.email = email }
// if let address = data["address"] as? [String: Any] {
// let street = address["street"] as? String
// let postCode = address["postCode"] as? String
// let city = address["city"] as? String
// if street != nil && postCode != nil && city != nil {
// self.address = Address(street: street!, postalCode: postCode!, city: city!)
// }
// }
}
What simple issue am I overlooking here?
You declare an initializer which promises to either return an initialized Customer or no Customer (because it is fallible). You alo declare let customerType: customerTypes as one of the properties of the class.
That means that if you successfully return from the initializer (that means, not returning nil), this property has to be initialized to some value.
The error is not very helpful in the location of the error, as the error is actually on the line below. By simply putting return in your guard, you are saying that your object is successfully initialized, which it is not, as you have not yet set customerType to a value.
So if you put a return nil in your guard clause, you will say that your initialization failed, and then you do not need to put a value in customerType.
The properties that don't have an initial value needs to set inside an init. You can fix the issue by either setting them as Optional or by setting a default value:
init?(data: [String: Any]) {
customerType = .consumer
name1 = ""
}
or:
var customerType: customerTypes?
var name1: String?
Note: By setting the properties Optional the compiler assumes that the initial value is nil.

Parsing a json array returns empty elements in swift

As suggested by OOPer I am posting this as a separate question. This is an extension of this:
JSONserialization error
I am pulling a json array from a php script that reads an sql database. The json is pretty large but should be well formed as tested using several online testers. The problem is that when I try to get the elements out of the array it returns nil. It is able to download the data and it can correctly count how many elements are in the array but when I try to access the elements it returns nil. Any suggestions?
Here is an example of an element:
{
"id":2045,
"oprettelsesdato":"09-02",
"overskrift":"etc etc etc",
"navn":"xyz xyz xyz",
"tlf":"12345678",
"email":"etc#etc.etc",
"journal":"MJ",
"intro":"yada yada yada yada ",
"annonce":"test",
"address":"testroad"
},
The LocationModel.swift
import Foundation
class LocationModel: NSObject {
//properties
var id: String?
var oprettelsesdato: String?
var overskrift: String?
var address: String?
var intro: String?
var email: String?
var tlf: String?
var annonce: String?
var journalnr: String?
override init()
{
}
init(id: String, oprettelsesdato: String, overskrift: String, address: String, intro: String, email: String, tlf: String, annonce: String, journalnr: String) {
self.id = id
self.oprettelsesdato = oprettelsesdato
self.overskrift = overskrift
self.address = address
self.intro = intro
self.email = email
self.tlf = tlf
self.annonce = annonce
self.journalnr = journalnr
}
override var description: String {
return "id: \(id), oprettelsesdato: \(oprettelsesdato), overskrift: \(overskrift), address: \(address), journalnr: \(journalnr)"
}
}
And here is where the error is thrown:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item: LocationModel = feedItems[indexPath.row] as! LocationModel
let cellIdentifier: String = "BasicCell"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
print(feedItems[indexPath.row])
//myCell.detailTextLabel!.text = item.oprettelsesdato
myCell.textLabel!.text = item.overskrift
myCell.textLabel!.leadingAnchor.constraint(equalTo: myCell.leadingAnchor).isActive = true
myCell.textLabel!.topAnchor.constraint(equalTo: myCell.topAnchor).isActive = true
myCell.textLabel!.heightAnchor.constraint(equalTo: myCell.heightAnchor,
multiplier: 1.0).isActive = true
myCell.textLabel!.widthAnchor.constraint(equalTo: myCell.heightAnchor,
multiplier: 0.8).isActive = true
//print(item.id) <-returns nil
//print(item.oprettelsesdato) <-returns nil
//print(item.overskrift) <-returns nil
extralabel!.text = item.oprettelsesdato // <-This is where the error is thrown
}
error msg:
fatal error: unexpectedly found nil while unwrapping an Optional value
update
So I narrowed it down to the following in the json parser. The if let optional is never true although all the jsonElements contain values. What is wrong?
for jsonElement in jsonResult {
print(jsonElement["id"]) //<- these print() all return the correct values
print(jsonElement["oprettelsesdato"])
print(jsonElement["overskrift"])
print(jsonElement["address"])
print(jsonElement["intro"])
print(jsonElement["email"])
print(jsonElement["tlf"])
print(jsonElement["annonce"])
print(jsonElement["journal"])
let location = LocationModel()
if let id = jsonElement["id"] as? String,
let oprettelsesdato = jsonElement["oprettelsesdato"] as? String,
let overskrift = jsonElement["overskrift"] as? String,
let address = jsonElement["address"] as? String,
let intro = jsonElement["intro"] as? String,
let email = jsonElement["email"] as? String,
let tlf = jsonElement["tlf"] as? String,
let annonce = jsonElement["annonce"] as? String,
let journalnr = jsonElement["journal"] as? String
{ //this never returns true and location. is never set. Why??
location.id = id
location.oprettelsesdato = oprettelsesdato
location.overskrift = overskrift
location.address = address
location.intro = intro
location.email = email
location.tlf = tlf
location.annonce = annonce
location.journalnr = journalnr
}
locations.append(location)
}
If the line
extralabel!.text = item.oprettelsesdato // <-This is where the error is thrown
Is the one throwing an "unexpectedly found nil while unwrapping an Optional value" error then the cause is almost certainly that extralabel (which should be named extraLabel to follow camelCase naming conventions) is nil. Set a breakpoint at that line and check extralabel.
According to the given JSON id is clearly Int, not String
var id: Int
...
if let id = jsonElement["id"] as? Int,
Note: Don't declare properties as optional when providing a non-optional initializer or as a laziness alibi not to write an initializer. The type system of Swift encourages you to use non-optional types as much as possible. Non-optional types will never crash the app!
Your var oprettelsesdato:String? variable is an optional variable .And as you no optionals says either "there is a value" or "there isn't a value at all".If you define variable as an optional ,then to get value from this variable you will have to unwrap it.And good way to force unwrap are OPTIONAL BINDING.Here is a example-:
var oprettelsesdato:String?
oprettelsesdato = "hello swift!"
if let value = oprettelsesdato
{
println(value)
}
else
{
println("no value")
}
TRY YOUR CODE IN THIS WAY IT WILL SOLVE YOUR ISSUE
JUST USE if let AND STORE ARRAY VALUES IN VARIABLE THEN ASSIGN THEM TO YOUR LABELS WITHIN if let CONDITION.
EXAMPLE-:
if let value = item.oprettelsesdato
{
extralabel.text = value
}
else
{
print("no value")
}

Variable doesn't store what given to it - Swift 3

I'm having trouble with storing a date that I got from SQLite DB into a Date Var, I'm trying to save the date coming from DB to the ResultTask.AlarmDate after converting it from String to Date.
When I tried and deleted the ? from ResultTask.AlarmDate? it stored the data fine, but when I have a task without an alarm I get an error cause I want to store nil it the var when there's no alarm, and tried multiple other ways to fix it but nothing work.
I made a break point right after the issue and used the Console to see the data and got confused, cause the data shown when I print what I'm trying to store in the var but the var is still nil, all shown below.
Notes
The code that getting data from DB works fine, I'm using the Objective-C FM-Database library, and result is seen in the Console below.
the .toDate() is an extension for String that I use and it code is in AppDelegate.swift as seen bellow, and it work just fine.
the Task Variable type is something I made in a Task.swift file to easy the work, and it work just fine & shown below.
The Function:
func GetTasks() -> [Task]{
var Tasks : [Task] = [Task]()
let dirPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask, true)
let docsDir = dirPaths[0]
databasePath = (docsDir + "/" + "Tasks.db") as NSString
let TaskRecorderDB = FMDatabase(path: databasePath as String)
if (TaskRecorderDB?.open())! {
let querySQL = "Select * from 'Tasks' ORDER BY Priority DESC"
let results:FMResultSet? = TaskRecorderDB?.executeQuery(querySQL, withArgumentsIn: nil)
while results!.next() {
var ResultTask : Task! = Task()
ResultTask.ID = Int((results?.int(forColumn: "ID"))!)
ResultTask.FileName = results?.string(forColumn: "FileName")
ResultTask.Name = results?.string(forColumn: "Name")
ResultTask.Categorie = Int((results?.int(forColumn: "Categorie"))!)
ResultTask.Priority = Int((results?.int(forColumn: "Priority"))!)
ResultTask.AlarmDate? = (results?.string(forColumn: "Alarm").toDate())!
//Break point
ResultTask.Repeat? = (results?.string(forColumn: "Repeat"))!
ResultTask.Completed = Int((results?.int(forColumn: "Completed"))!).toBool()
if(ResultTask.Completed == false){
Tasks.append(ResultTask)
}
ResultTask = nil
}
TaskRecorderDB?.close()
} else {
print("Error #DB06: \(TaskRecorderDB?.lastErrorMessage())")
}
return Tasks
}
Console:
(lldb) print (results?.string(forColumn: "Alarm").toDate())!
(Date) $R0 = 2016-11-23 17:21:00 UTC
(lldb) print ResultTask.AlarmDate
(Date?) $R1 = nil
(lldb)
AppDelegate.swift:
extension String {
func toDate () ->Date! {
var FinalDate : Date! = nil
if (self != ""){
let DateFormatter : Foundation.DateFormatter = Foundation.DateFormatter()
DateFormatter.locale = Locale.current
DateFormatter.dateFormat = "y-MM-dd_HH-mm-ss"
FinalDate = DateFormatter.date(from: self)!
}
return FinalDate
}
}
Task.swift:
import Foundation
class Task : NSObject {
var ID : Int! = nil
var FileName : String! = nil
var Name : String! = nil
var Categorie : Int! = nil
var Priority : Int! = nil
var AlarmDate : Date! = nil
var Repeat : String! = nil
var Expired : Bool! = nil
var Completed : Bool! = nil
}
Thanks in advance.
Edit for #KaraBenNemsi :
I updates the .toDate() as you said.
& in task I used var AlarmDate : Date? instead of var AlarmDate : Date! = nil (I didn't use you're other tip in Task.swift for now cause I'll need to change a lot of other code in the app).
And used ResultTask.AlarmDate = results?.string(forColumn: "Alarm").toDate() and I get a fatal error: unexpectedly found nil while unwrapping an Optional value in it when the task doesn't have an alarm.
Try to change this line in the task class since it can be optional
from this
var AlarmDate: Date! = nil
to this
var AlarmDate: Date?
And then use this line to set the alarm
ResultTask.AlarmDate = results?.string(forColumn: "Alarm")?.toDate()
Also variable names should usually start with a lowercase letter.
I also think this line is unnecessary. You can just omit that line.
ResultTask = nil
Please also change the extension to
extension String {
func toDate() -> Date? {
guard self != "" else {
return nil
}
var finalDate: Date?
let DateFormatter : Foundation.DateFormatter = Foundation.DateFormatter()
DateFormatter.locale = Locale.current
DateFormatter.dateFormat = "y-MM-dd_HH-mm-ss"
finalDate = DateFormatter.date(from: self)
return finalDate
}
}
And maybe try to get more into how optionals work
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html
Addition:
Your class task should probably also look more like below. But then you have to change code in other places as well. It also forces you to pass all the required parameters in the constructor when initialising the task object.
class Task {
var id: Int
var fileName: String
var name: String
var category: Int
var priority: Int
var alarmDate: Date?
var repeating: String
var expired: Bool
var completed: Bool
init(id: Int, fileName: String, name: String, category: Int, priority: Int, repeating: String, expired: Bool, completed: Bool) {
self.id = id
self.fileName = fileName
self.name = name
self.category = category
self.priority = priority
self.repeating = repeating
self.expired = expired
self.completed = completed
}
}
And here is also a link to a Swift style guide:
https://github.com/raywenderlich/swift-style-guide
Change your task to have this
var AlarmDate : Date?
And then do this
if let alarm = results?.string(forColumn: "Alarm").toDate() {
ResultTask.AlarmDate = alarm
}
If any nil is found while unwrapping results, you won't set the value of ResultTask.AlarmDate.

How to fix: "fatal error: unexpectedly found nil while unwrapping an Optional value (lldb)"

I am working on a Firebase Swift project using CocoaPods.
Every time after I log in the main ViewController, automatically I get EXC_BREAKPOINT error:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Here are some of my code lines where I got errors:
All codes from Joke.swift:
import Foundation
import Firebase
class Joke {
private var _jokeRef: Firebase!
private var _jokeKey: String!
private var _jokeText: String!
private var _jokeVotes: Int!
private var _username: String!
var jokeKey: String {
return _jokeKey
}
var jokeText: String {
return _jokeText
}
var jokeVotes: Int {
return _jokeVotes //1
}
var username: String {
return _username
}
// Initialize the new Joke
init(key: String, dictionary: Dictionary<String, AnyObject>) {
self._jokeKey = key
// Within the Joke, or Key, the following properties are children
if let votes = dictionary["votes"] as? Int {
self._jokeVotes = votes
}
if let joke = dictionary["jokeText"] as? String {
self._jokeText = joke
}
if let user = dictionary["author"] as? String {
self._username = user
} else {
self._username = ""
}
// The above properties are assigned to their key.
self._jokeRef = DataService.dataService.JOKE_REF.childByAppendingPath(self._jokeKey)
}
// Add or Subtract a Vote from the Joke.
func addSubtractVote(addVote: Bool) {
if addVote {
_jokeVotes = _jokeVotes + 1
} else {
_jokeVotes = _jokeVotes - 1
}
// Save the new vote total.
_jokeRef.childByAppendingPath("votes").setValue(_jokeVotes)
}
}
In JokeCellTableViewCell.swift:
var joke: Joke!
...............
func configureCell(joke: Joke) {
self.joke = joke
// Set the labels and textView.
self.jokeText.text = joke.jokeText
self.totalVotesLabel.text = "Total Votes: \(joke.jokeVotes)" // 2
self.usernameLabel.text = joke.username
// Set "votes" as a child of the current user in Firebase and save the joke's key in votes as a boolean.
.........
}
And in the main ViewController, JokesFeedTableViewController.swift:
var jokes = [Joke]()
....................
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let joke = jokes[indexPath.row]
// We are using a custom cell.
if let cell = tableView.dequeueReusableCellWithIdentifier("JokeCellTableViewCell") as? JokeCellTableViewCell {
// Send the single joke to configureCell() in JokeCellTableViewCell.
cell.configureCell(joke) // 3
return cell
} else {
return JokeCellTableViewCell()
}
...........
// 1, // 2, // 3 are code lines where errors appear.
I hope you could help me to fix this!
Thank you in advance!
Your problem is that you have not clearly defined the expectations of the Joke class.
Your initializer suggests that the properties on Joke should be optional, however, you are using them as though they are not. You must decide on which way you want to take it.
If the properties can be optional, I would suggest something like this:
class Joke {
private let jokeReference: Firebase
let jokeKey: String
private(set) var jokeText: String?
private(set) var jokeVotes: Int?
let username: String
// Initialize the new Joke
init(key: String, dictionary: Dictionary<String, AnyObject>) {
jokeKey = key
// Within the Joke, or Key, the following properties are children
if let votes = dictionary["votes"] as? Int {
jokeVotes = votes
}
if let joke = dictionary["jokeText"] as? String {
jokeText = joke
}
if let user = dictionary["author"] as? String {
username = user
} else {
username = ""
}
// The above properties are assigned to their key.
jokeReference = DataService.dataService.JOKE_REF.childByAppendingPath(jokeKey)
}
}
However, if the properties should never be nil, you need something like this:
class Joke {
private let jokeReference: Firebase
let jokeKey: String
let jokeText: String
let jokeVotes: Int?
let username: String
// Initialize the new Joke
init?(key: String, dictionary: Dictionary<String, AnyObject>) {
jokeKey = key
guard let votes = dictionary["votes"] as? Int,
joke = dictionary["jokeText"] as? String else {
return nil
}
jokeText = joke
jokeVotes = votes
if let user = dictionary["author"] as? String {
username = user
} else {
username = ""
}
// The above properties are assigned to their key.
jokeReference = DataService.dataService.JOKE_REF.childByAppendingPath(jokeKey)
}
}

Error handling parsing JSON in Swift

I'm using Alamofire and am parsing the returned JSON into an object as shown below:
final class User: NSObject, ResponseObjectSerializable {
var id: Int
var facebookUID: String?
var email: String
var firstName: String
var lastName: String
var phone: String?
var position: String?
var timeCreated: CVDate
init?(response: NSHTTPURLResponse, var representation: AnyObject) {
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [String: AnyObject]) {
representation = dataRepresentation
}
if let id = representation.valueForKeyPath("id") as? Int {
self.id = id
} else {
self.id = 0
}
if let facebookUID = representation.valueForKeyPath("facebook_UID") as? String {
self.facebookUID = facebookUID
}
if let email = representation.valueForKeyPath("email") as? String {
self.email = email
} else {
self.email = ""
}
if let firstName = representation.valueForKeyPath("first_name") as? String {
self.firstName = firstName
} else {
self.firstName = ""
}
if let lastName = representation.valueForKeyPath("last_name") as? String {
self.lastName = lastName
} else {
self.lastName = ""
}
if let phone = representation.valueForKeyPath("phone") as? String {
self.phone = phone
}
if let position = representation.valueForKeyPath("position_name") as? String {
self.position = position
}
if let timeCreated = representation.valueForKeyPath("time_created") as? String {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
if let date = formatter.dateFromString(timeCreated) {
self.timeCreated = CVDate(date: date)
} else {
self.timeCreated = CVDate(date: NSDate())
}
} else {
self.timeCreated = CVDate(date: NSDate())
}
}
}
My question is, is this style the best way to decode JSON and set the non-optional instance variables? For example, in this statement:
if let id = representation.valueForKeyPath("id") as? Int {
self.id = id
}
I am required by the compiler to add an else clause and set the id to something otherwise xCode throws an error saying: self.id is not initialized at implicitly generated super.init call.
But at the same time, intializing self.id with a value of 0 is wrong and doesn't help me at all.
But at the same time, intializing self.id with a value of 0 is wrong and doesn't help me at all.
If having a default value for self.id feels wrong, then you should make this property an Optional. That way you wouldn't have to add an else clause:
final class User: NSObject, ResponseObjectSerializable {
var id: Int?
var facebookUID: String?
var email: String
var firstName: String
var lastName: String
var phone: String?
var position: String?
var timeCreated: CVDate
init?(response: NSHTTPURLResponse, var representation: AnyObject) {
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [String: AnyObject]) {
representation = dataRepresentation
}
if let id = representation.valueForKeyPath("id") as? Int {
self.id = id
}
...
Update
You said in the comments:
I always need to have an id for the user object though.
If you have to have this id property then the question is moot, you just have to do
let id = representation.valueForKeyPath("id") as! Int
and guarantee earlier that this value will exist.
Because if your object needs an ID, then you can't initialize it anyway if this value doesn't exist and if you don't want a default value.
You could use ?? to provide default values like this:
self.id = (representation.valueForKeyPath("id") as? Int) ?? 0
While the ResponseObjectSerializable code is a great example from the Alamofire project, it's really a better idea to use a dedicated JSON parsing library that has actual error states. This is far better than using optionals to represent error states, or having to provide a default value for every field just in case the response isn't correctly formed.
Although it has a bit of learning curve, I prefer to use Argo for my JSON parsing. Once you get the hang of it it makes JSON parsing practically bulletproof. Better yet, it's easy to integrate with Alamofire, especially version 3 that was released today.
To address your concern about not having an ID being an error condition, you could use a failable initializer. I did that in a recent project. Looks something like this:
let id: Int!
init? (inputJson: NSDictionary) {
if let id = inputJson["id"] as? Int {
self.id = id
} else {
// if we are initing from JSON, there MUST be an id
id = nil
cry(inputJson) // this logs the error
return nil
}
}
Of course, this means your code will need to accept that the initialization of your entire object may fail ..

Resources