2

I'm making an app for archiving books, I'm at the point of hooking up a button to save the data, but I can't get past the initialisation.

Here is the error Xcode is giving me :

Property 'self.bookStore' not initialised at super.init call.

And here is my code :

import UIKit

class AddBook: UIViewController {

    @IBOutlet weak var bookAuthor: UITextField!
    @IBOutlet weak var bookTitle: UITextField!

    let bookStore: BookStore

    init(bookStore: BookStore) {
        self.bookStore = bookStore
        super.init(nibName: nil, bundle: nil)
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder) // Error: Property 'self.bookStore' not initialised at super.init call.
    }


    @IBAction func saveNewBook(sender: AnyObject) {
        let author = self.bookAuthor.text!
        let title = self.bookTitle.text!

        bookStore.addBook(author, title: title)
        println("\(bookStore)")
    }
}


import UIKit

class BookStore: NSObject {

    var allBooks: [Book] = []

    func addBook(author: String, title: String) -> Book {
        let newBook = Book(author: author, title: title)

        allBooks.append(newBook)

        return newBook
    }
}
2
  • You need to set self.bookStore to something before you call super.init Commented Jun 22, 2015 at 20:18
  • I don't understand what I'm supposed to set it too. BookStore is a class with an array that will hold all the books & the methods for adding / deleting etc. Commented Jun 22, 2015 at 20:22

3 Answers 3

2

Your custom initializer cannot initialize the immutable property. If you want it to be immutable then, instead of creating a custom initializer, just initialize in one of the required or designated initializer.

Like this,

class AddBook: UIViewController {

    @IBOutlet weak var bookAuthor: UITextField!
    @IBOutlet weak var bookTitle: UITextField!

    let bookStore: BookStore

    required init(coder aDecoder: NSCoder) {
        fatalError("Cannot be instantiated from storyboard")
    }

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        bookStore = BookStore()
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }


    @IBAction func saveNewBook(sender: AnyObject) {
        let author = self.bookAuthor.text!
        let title = self.bookTitle.text!

        bookStore.addBook(author, title: title)
    }
}

Or this,

class AddBook: UIViewController {

    @IBOutlet weak var bookAuthor: UITextField!
    @IBOutlet weak var bookTitle: UITextField!

    let bookStore: BookStore

    required init(coder aDecoder: NSCoder) {
        bookStore = BookStore()
        super.init(coder: aDecoder)
    }

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        fatalError("Cannot be instantiated from code")

    }


    @IBAction func saveNewBook(sender: AnyObject) {
        let author = self.bookAuthor.text!
        let title = self.bookTitle.text!

        bookStore.addBook(author, title: title)
    }
}

But, in your case, you want it to be immutable as you are using let bookStore: BookStore but if you were not using storyboard you could simply throw fatal exception from the required initializer and use your own initializer like this,

class AddBook: UIViewController {

    @IBOutlet weak var bookAuthor: UITextField!
    @IBOutlet weak var bookTitle: UITextField!

    let bookStore: BookStore

    init(bookStore: BookStore) {
        self.bookStore = bookStore
        super.init(nibName: nil, bundle: nil)
    }

    required init(coder aDecoder: NSCoder) {
        fatalError("cannot be initialized from storyboard")
    }

    @IBAction func saveNewBook(sender: AnyObject) {
        let author = self.bookAuthor.text!
        let title = self.bookTitle.text!

        bookStore.addBook(author, title: title)
    }
}


let book = AddBook(bookStore: BookStore())

Now, that you need to have the required initializer, you have to make the property mutable such that you can set it from custom initializer like this,

class AddBook: UIViewController {

    @IBOutlet weak var bookAuthor: UITextField!
    @IBOutlet weak var bookTitle: UITextField!

    var bookStore: BookStore!

    init(bookStore: BookStore) {
        self.bookStore = bookStore
        super.init(nibName: nil, bundle: nil)
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    @IBAction func saveNewBook(sender: AnyObject) {
        let author = self.bookAuthor.text!
        let title = self.bookTitle.text!

        bookStore.addBook(author, title: title)
    }
}


let book = ... // initialized from storyboard or using performSegueWithIdentifier:

book.bookStore =  BookStore()

The main philosophy here is, no two initializer can initialize the immutable properties. Only one can do, so either use required initializer to instantiate it or make required initializer unusable or then make the property mutable such that you can have two initializer and both can initialize it.

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for your answer. How would I go about implementing this so that I could create the BookStore() in the App Delegate. Since at the moment whenever I go to a different view & come back its recreated, thus all the data is lost. I tried to modify it to work like this but I'm not having much luck.
Do you use storyboard for this view controller ?
Yeah I did, is that what's causing me all the grief?
@AndrewKing, In that case you could use the last example code, I have and it should be fine.
Many thanks for you help. Just a final quick, now that BookStore is optional, my bookStore.addBook(author, title: title) is causing a crash because it's finding nil. I thought all I had to do was add an ! but it seems not. Any thoughts?
|
0

Required properties must be defined in your init method(s) before you call super.init.

So, if you want a required bookStore property, and you want to override the init.coder method, you need to assign a value to bookStore prior to calling super.init:

required init(coder aDecoder: NSCoder) 
{
   bookStore = //some value here
   super.init(coder: aDecoder) 
}

Probably the simplest thing to do is to make your bookStore property an optional var:

var bookStore: BookStore?

If you do that you won't have to assign a value to your view controller's bookstore property at init time and can do it later on in setting up a view controller for display.

Back to initWithCoder():

initWithCoder (or init(coder) as it's called in Swift) is a method that is supposed to deserialize an object.

iOS uses initWithCoder to read interface objects from XIB files and storyboards.

The idea is that you write custom versions of initWithCoder and encodeWithCoder that know how to read/write the object's custom properties so it can be saved to a file.

In encodeWithCoder you write code that saves your object's custom properties as key/value pairs into the archiver. Then in initWithCoder you read those key/value pairs back from the archiver using the methods of NSKeyedUnarchiver to read those values.

Comments

0

I don't see why you need the init but the rule is all properties must have values before calling super.init (bookStore doesn't). I think you can fix that simply like let bookStore = BookStore()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.