Reputation: 100
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
}
}
Upvotes: 2
Views: 2297
Reputation: 21144
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.
Upvotes: 2
Reputation: 3433
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()
Upvotes: 0
Reputation: 131426
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.
Upvotes: 0