I have an object called navigator, which I set within init. I break on it to make sure it is set. However when an IBAction func, linkButtonClicked, get's called and try's to use navigator I get a nil exception. Why?
class HomeCollectionViewCell: UICollectionViewCell {
let appDelegate:AppDelegate!
let navigator: Navigator!
#IBOutlet weak var linkButton: UIButton!
var destinationView:String?
var parentViewController:UIViewController?
#IBAction func linkButtonClicked(_ sender: Any) {
do {
try self.navigator.navigate(to: self.destinationView!, from: parentViewController!)
} catch {
}
}
required init?(coder aDecoder: NSCoder) {
self.appDelegate = UIApplication.shared.delegate as! AppDelegate
self.navigator = self.appDelegate.navigator
super.init(coder: aDecoder)
}
override func prepareForReuse() {
super.prepareForReuse()
// do resetting here if needed, like empty out data
linkButton.setTitle(nil, for: .normal)
}
}
The init?(coder: NSCoder) initializer gets used when you are retrieving the object from some kind of encoded store such as Core Data. This initializer is required by the NSCoding protocol and is used only for deserializing the object. Therefore, it does not get called at object creation. It only gets called if you serialize the object using NSCoding and later deserialize it.
The function you want to override in order to ensure some value will be set in your view is not its init (and if you really want to use its init, the method to overload is init(frame:)). Instead, you should set any variables you want to be available in the viewDidLoad method of the view controller.
Related
I'm trying to make this code work:
class MyWindowController: NSWindowController
{
let thing: Thing
convenience init(thing: Thing)
{
self.thing = thing
super.init(windowNibName: NSNib.Name(rawValue: "MyNib"))
}
}
The problem, of course, is that a convenience initializer can't call init from a superclass. So how do I initialize my thing and still be able to call init(windowNibName:), which is itself a convenience initializer? I'd rather not have to re-implement the nib loading myself, but how do I avoid it if I can only use designated initializers?
According to the NSWindowController documentation:
You can also implement an NSWindowController subclass to avoid requiring client code to get the corresponding nib's filename and pass it to init(windowNibName:) or init(windowNibName:owner:) when instantiating the window controller. The best way to do this is to override windowNibName to return the nib's filename and instantiate the window controller by passing nil to init(window:). Using the init(window:) designated initializer simplifies compliance with Swift initializer requirements.
You can implement your class as:
class MyWindowController: NSWindowController
{
let thing: Thing
override var windowNibName: NSNib.Name? {
return NSNib.Name(rawValue: "MyNib")
}
init(thing: Thing) {
self.thing = thing
super.init(window: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Summary:
What's the proper way to save/restore the state of an NSSearchField in my NSViewController using the built-in user interface preservation mechanism of Cocoa?
Details:
I'm working on my first macOS app and I'm having a little trouble with state restoration for my user interface. So far I have it working to the point where the encodeRestorableState(with:) and restoreState(with:) methods are called in my NSViewController subclass.
I have an NSSearchField in my view controller and I want to save/restore the state of the search field including its text, any selection, the cursor position, and whether it is currently in focus or not.
If I use the following code, the text is properly saved and restored:
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
coder.encode(searchField.stringValue, forKey: "searchText")
}
override func restoreState(with coder: NSCoder) {
super.restoreState(with: coder)
if let searchText = coder.decodeObject(forKey: "searchText") as? String {
searchField.stringValue = searchText
}
}
Obviously I can add more code to save/restore the search field's selection and cursor position, etc.
My real question is, is there a better, proper, more automatic way to save and restore the search field's state? Or is it required that I write my own code for each attribute of the search field I wish to save?
I tried using:
searchField.encodeRestorableState(with: coder)
and:
searchField.restoreState(with: coder)
in the two above methods but that didn't result in anything appearing in the search field when my app was restarted.
I also implemented:
override class func restorableStateKeyPaths() -> [String] {
var keys = super.restorableStateKeyPaths()
keys.append("searchField.stringValue")
return keys
}
where "searchField" is the name of the NSTextField outlet property in my view controller. This method is called when my app is launched but the text was not restored in the search field.
This view controller is created from a storyboard. The view controller's identifier is set. The search field's identifier is set as well. This view controller is a child view controller of another view controller.
I've read through the User Interface Preservation section of the "The Core App Design" document but it's unclear on how a view controller's views are saved/restored and how much of this is automatic versus manual.
Supporting OSX 10.12 and 10.11. Objective-C or Swift in any answers is fine.
To achieve aforementioned effect let's create NSSearchField and a custom class named RestoredWindow containing just one property:
import Cocoa
class RestoredWindow: NSWindow {
override class var restorableStateKeyPaths: [String] {
return ["self.contentViewController.searchField.stringValue"]
}
}
Assign this custom class to Window Controller in Identity Inspector.
Next, let's bind searchField.stringValue property to ViewController in Value section of Bindings Inspector.
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var searchField: NSSearchField!
override func viewDidLoad() {
super.viewDidLoad()
}
}
After this, make sure you haven't checked Close windows when quitting an app option in your System Preferences -> General tab.
Now, entered text in NSSearchField restored after quitting and launching the app again.
P.S.
I've tried the same way as you but failed too. This approach doesn't work for me:
override func encodeRestorableState(with coder: NSCoder) {
coder.encode(searchField.stringValue, forKey: "restore")
super.encodeRestorableState(with: coder)
}
override func restoreState(with coder: NSCoder) {
if let state = coder.decodeObject(forKey: "restore") as? NSSearchField {
searchField.stringValue = state
}
super.restoreState(with: coder)
}
In Objective-C I had evolved the pattern of having both an awakeFromNib and initWithFrame: method which invoked their super's and then called a _commonInit where I put all my own code. E.g.
- (void)_commonInit {
// Initialize stuff here
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self _commonInit];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self _commonInit];
}
So I'm trying to reuse this pattern in my UIView subclasses that I'm porting to Swift:
func _commonInit() {
// initialize code here
}
override init(frame:CGRect) {
super.init(frame:frame)
self._commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self._commonInit()
}
Is this the right way to do it? I'm curious why the init(coder...) is required. Especially when all I do is call the super version. I seem to recall that the reason I used awakeFromNib in the Objc version was because any changes applied from nib restoration didn't happen until sometime later than initFromCoder:.
Having a commonInit method that gets called from your initializers is a perfectly fine pattern for cases where you have more than one designated (or required) initializer. It's not the only pattern, though.
To address each of your doubts in turn (and add some related points)...
init(coder:) is required because you're subclassing a class that declares conformance to the NSCoding protocol. That protocol demands that all instances of all subclasses be able to initialize from an archive.
You don't actually have to do anything in init(coder:) unless you save state in encodeWithCoder(_:), though. But because it's possible for a subclass to have encodable state that's not inherited, initialization safety requires that the subclass be responsible for this initializer (even if all it does is call super).
You use awakeFromNib() in any custom class loaded from a nib/storyboard to take care of initialization that needs to happen only after an object's outlets and actions have been hooked up.
When Cocoa (Touch) loads a nib, it first initializes each object (with init(coder:)), then after all the objects are "live", it connects all the IBOutlet variables and the targets for all of the IBActions sent by controls. After all that's done, it calls awakeFromNib().
There's a caveat—a paradox, even—to using the commonInit pattern in Swift: You must initialize properties (aka instance variables) before calling super.init(), and you can't access self (including to call methods on self) until after calling super.init(). So you can't have a commonInit method that sets the initial values of properties.
You can have properties whose types are implicitly unwrapped optionals. Those are automatically initialized to nil, and then you can set a "real" initial value in your commonInit method:
var name: String!
init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
init(coder: NSCoder) {
super.init(coder: coder)
self.commonInit()
}
private func commonInit() {
name = // something...
}
An alternative pattern that gets around this issue is to supply initializers (or even lazy initializers) for each of your properties. If you do this, you don't need a commonInit method, because the property initializers will be implicitly called from whichever init actually gets used (or in the case of lazy initializers, when the property is first accessed).
class MyView: UIView {
let fileManager = NSFileManager.defaultManager()
let appDelegate = UIApplication.sharedApplication().delegate as! MyAppDelegate
lazy var name: String = { /* compute here */ }()
init(frame: CGRect) { super.init(frame: frame) }
init(coder: NSCoder) { super.init(coder: coder) }
// ...
}
Finally, if you do provide a commonInit (or similar) method, you don't need to mangle the name with an initial underscore or anything—Swift has built-in access control, so any methods you don't want exposed to callers outside of a class can simply be marked private.
I have a UITableViewController subclass, that should not work if there is no Model. If there is no Model, there isn't really any purpose in showing the View.
So I'm thinking, my model property should not be an optional value. I want this kind of safety. So i'm trying to create a convenience init that passes my model along.
let model:Client
override init() {
super.init(style: UITableViewStyle.Plain)
}
convenience init(model:Client) {
self.init()
self.model = model
}
My problem is that I'm getting this error:
Property 'self.model' not initialised at super.init call
This makes sense. If init() were to be called, the property would not be set, as required by a non-optional property.
I do I overcome this?
Bear in mind, that model is my actual Model, setting a default value here would be pointless, and again, defeat the safety that I'm looking for.
Thank you!
Small note: doing this, will not work also. There is no instance to set the model anyway.
convenience init(model:Client) {
self.model = model
self.init()
}
Edit: The approach bellow seemed promising
let model: Client
required init(coder aDecoder: NSCoder) {
preconditionFailure("Cannot initialize from coder")
}
init(model:Client) {
self.model = model
super.init(style: UITableViewStyle.Plain)
}
however, it gets me this error:
fatal error: use of unimplemented initializer 'init(nibName:bundle:)'
And finally, the solution here was to call:
super.init(nibName: nil, bundle: nil)
You don't need to override init() here, and you want to make init(model:) your designated initializer (not a convenience). You can do that like this
let model: Client
required init(coder aDecoder: NSCoder) {
preconditionFailure("Cannot initialize from coder")
}
init(model:Client) {
self.model = model
super.init(style: UITableViewStyle.Plain)
}
I have a function in MasterViewController
func removeLocation(city: String){
objects.removeObject(city)
self.tableView.reloadData()
}
In my DetailViewController I check whether the city is valid and if its not, I want to remove it from the table in MasterViewController. I pass self in prepareForSegue() from MasterView to DetailView and I assign it to
var masterViewController: MasterViewController
But then I get an error saying that it is not initialized and it want me to have this initializer
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Which breaks my whole program when I run it and gives me an expected fatal error.
Ho I can create an instance of MasterView in DetailView without an initializer, or access a function in MasterView from DetailView?
This is Swift btw, I found some advices on how to solve it in C but I couldn't implement them.
Use an optional.
var masterViewController: MasterViewController?
Optionals do not have to be initialized when the class is created.
In prepareForSegue() assign the pointer as usual:
destinationViewController.masterViewController = self
Then when you need to call removeLocation:
masterViewController?.removeLocation("London")