NSViewController User Interface State Restoration
Asked Answered
E

1

34

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.

Endorse answered 12/12, 2016 at 15:38 Comment(8)
What about "Alternatively, your custom responder objects can override the restorableStateKeyPaths method and use it to specify key paths for any attributes to be preserved. Cocoa uses the key paths to locate and save the data for the corresponding attribute. Attributes must be compliant with key-value coding and Key-value observing."?Superjacent
@LeoNatan I just tried that. I implemented that method in my view controller and returned the name of the NSTextField attribute. The method is called but the text field is not restored.Endorse
I think it should include the .stringValue portion of the key path. It should drill down to the value itself, not the control.Superjacent
@LeoNatan No, that didn't work either. I updated that code to return "searchField.stringValue".Endorse
If you are setting the stringValue yourself before restoration, it seems the restoration process won't override with store value. "The above step isn’t necessary when using +restorableStateKeyPaths because KVO handles it for us." bignerdranch.com/blog/cocoa-ui-preservation-yallSuperjacent
@LeoNatan The only thing I do to the search field is set its target and action and respond to the user changing its value. I never set its stringValue.Endorse
NSTextField doesn't implement restoration methods. Is stringValue KVO compliant?Detroit
The answers and comments to this question suggest that state restoration for NSViewController used to be problematic. In the sample code "State Restoration of Child View Controllers", Apple adopts your first method to restore the state of a UITextField (albeit on iOS where there is no equivalent of restorableStateKeyPaths). Perhaps you can subclass NSSearchField and implement restorableStateKeyPaths there.Foothill
H
2

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)
}
Howlan answered 15/5, 2018 at 22:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.