Property List Serialization

Hi,

I found have the following bit of code (iOS, Swift 3) that I got from an online tutorial but am having trouble making it work in swift 3. Any ideas what exactly needs changing?:


var notesArray:NSMutableArray!
    var plistPath:String!

    override func viewWillAppear(_ animated: Bool) {
    
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        plistPath = appDelegate.plistPathInDocument
        /
        let data:NSData =  NSFileManager.defaultManager().contentsAtPath(plistPath)!
        do{
            notesArray = try NSPropertyListSerialization.propertyListWithData(data, options: NSPropertyListMutabilityOptions.MutableContainersAndLeaves, format: nil) as! NSMutableArray
        }catch{
            print("Error occured while reading from the plist file")
        }
        self.tableView.reloadData()
    }


The plistPathInDocument is setup in app delegate and set to the path of the plist already. This method is essentially trying to populate a table view with data from a plist. Errors come from a few lines and suggests replacements making the code look like this:


var notesArray:NSMutableArray!
    var plistPath:String!
  
    override func viewWillAppear(_ animated: Bool) {
      
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        plistPath = appDelegate.plistPathInDocument
        /
        let data:NSData =  FileManager.default.contents(atPath: plistPath)! as NSData
        do{
            notesArray = try PropertyListSerialization.propertyList(from: data as Data, options: PropertyListSerialization.MutabilityOptions.mutableContainersAndLeaves, format: nil) as! NSMutableArray
        }catch{
            print("Error occured while reading from the plist file")
        }
        self.tableView.reloadData()
    }


This gets rid of any errors prior to running it however once run in the simulator I get a "Thread 1: signal SIGABRT" error on line 11 after it crashes. Any ideas how to fix this would be appreciated.


Thanks

Owen

Hi,

Have added the override keyword to the second data source method however the first one still shows an error when I add the override keyword: "Method does not override any method from its superclass". I tried running it with just the second data source method set as an override func and I get the SigaBRT error on the appdelegate file and the debugger says: "terminating with uncaught exception of type NSException".

Thanks

Owen

The indexPath parameter has type IndexPath, not NSIndexPath, in Swift 3.


By specifying NSIndexPath, you're declaring a different method to the one which will override a superclass method, and which will be called in populating the table. If you start typing the method signature into Xcode editor, autocomplete will suggest the correct signature for the version of Swift that it's using.

Hi,

It has now allowed me to add the override keyword and the method is being executed, thank you 🙂

However now it has revealed another error in the method. It crashes on line 6 of this table view method:


override func tableView(_ tableView: UITableView,
                            cellForRowAt indexPath: IndexPath) -> UITableViewCell{
       
        let cell:UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier")
        print(notesArray) //Prints notesArray perfectly in debugger here
        cell.textLabel!.text = notesArray[indexPath.row] as? String ///Crashes here
        return cell
    }


The error given in the debugger is:

fatal error: unexpectedly found nil while unwrapping an Optional value


Not too sure what this is caused by, sorry this is dragging on so long.

Thanks

Owen

Well, since this is the first time this method is actually getting run, I'm not surprised that there are issues in it. Probably you just forgot to connect the textLabel outlet in your Interface Builder item prototype. If you

print(cell.textLabel)
, what do you get?

I think I may have forgotten to, if I print that I get the same error but for the line that i print the cell.textLabel instead. What is it I need to do to link it?

Thanks

Owen

Ok, had a bit of a mess around with it and seemed to have got it a little better, the error is now:


Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier ViewControllerCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'


on line 42, I have now made a new subclass called ViewControllerCell that the label on the cell is linked to as cellLabel.

import UIKit
class ViewController: UITableViewController {
  
    var notesArray: [Any]!
    var plistPath:String!
  
    override func viewWillAppear(_ animated: Bool) {
      
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        plistPath = appDelegate.plistPathInDocument
        /
        let data = FileManager.default.contents(atPath: plistPath)!
      
      
        enum ReadError: Error { case CastFailed }
        do {
            let propertyList = try PropertyListSerialization.propertyList(from: data, options: .mutableContainersAndLeaves, format: nil)
            guard let convertedPropertyList = propertyList as? [Any] else { throw ReadError.CastFailed }
            notesArray = convertedPropertyList
        } catch{
            print("Error occured while reading from the plist file")
        }
        self.tableView.reloadData()
        Swift.print("The unpacked notes array is ", notesArray ?? "(failed to load)")
        print(plistPath)
        let now = Date()
        print(now)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        /
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        /
    }
  
    override func tableView(_ tableView: UITableView,
                            cellForRowAt indexPath: IndexPath) -> UITableViewCell{
        let cellIdentifier = "ViewControllerCell"
        print("test")
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ViewControllerCell
        let item = notesArray[indexPath.row] as? String
        cell.cellLabel?.text = item
        return cell
    }
  
    override func tableView(_ tableView: UITableView,
                            numberOfRowsInSection section: Int) -> Int{
        return notesArray.count
    }
  
}


Any Ideas?

Thanks

Owen

There is a way to easily link class names with reuse identifiers, but it's generally for more advanced capability that what I think you're going for. I'm inclined to think that the ViewControllerCell thing is a bit of a red herring. Here's what you should do to get it working:

Remove the cast to ViewControllerCell in line 42. All you need is this:

let cell = tableView.dequeueReusableCell(withIdentifier: "ViewControllerCell", for: indexPath)
  • Find your table view controller in your Interface Builder storyboard.
  • Look at the top of the table. It should say "PROTOTYPE CELLS" at the top and show some vanilla-looking placeholder(s) underneath. If it doesn't, click the table view and in the Attributes inspector (or press ⌥⌘4) switch the "Content" option to "Dynamic Prototypes."
  • Now select the cell itself and take a look at its Attributes inspector. If the cell doesn't look the way you want, you can choose from several style templates or select Custom and arrange it however you like.
  • Make sure that the cell's Identifier is ViewControllerCell or whatever identifier you want to use. All that matters is that the identifier used in your code matches the one specified here.
  • Oh, and it's
    cell.textLabel
    , not
    cell.cellLabel
    .
  • You should be all set to go. Build and run and see what happens!

Hi,

Thanks for these edits, I have done them and getting this error "terminating with uncaught exception of type NSException".

Just to clarify, am I meant to have added a label to the prototype cell and if so where am I supposed to link it too. Also am I supposed to have made a seperate sublass of the table view controller for the table cells?

Thanks

Owen

Does it say anything about what the exception is?


Did you try creating a custom label in IB or did you just use a standard template, like the Basic style? With the standard templates, the textLabel is connected automatically. With a custom pattern, you'd have to create a subclass of UITableViewCell (NOT UITableViewController) and define the custom outlet there.

Hi,

Thank you so much, the issue was, like you said, that I hadnt chosen that basic style for the cell and so it wasnt linking correctly, since i've done that it runs perfectly. Thank you for your help with this and sorry it took so long, still got a fair bit to add to this project so may get stuck with somethings again and would really appreciate your help if I do.

Thanks again

Owen

Glad to help. If you have more questions later on, I and others will be here! 🙂 Just please start a new thread for different issues—it's easier to keep separate issues on separate threads (and we've kind of filled this one up, don't you think?)

Property List Serialization
 
 
Q