How to make instantiateController(identifier: creator:) work? [duplicate] - macos

I am getting what feels like a bug when trying to custom instantiate a window controller from a storyboard. I am using NSStoryboard.instantiateController(identifier:creator:), which is a new function as of MacOS 10.15. The block of code in question is:
let mainWC = storyboard.instantiateController(identifier: "id") { aDecoder in
MainWindowController(coder: aDecoder)
}
I have SUCCESSFULLY used basically this exact code for custom instantiating the main view controller, and just assigning that view to a new window and a new window controller. That works fine. I can also instantiate the window controller the old fashioned way without custom initialization with instantiateController(identifier:). But when I try the above code for custom instantiation of the window controller I end up with the following error:
Assertion failure in -[NSClassSwapper _createControllerForCreator:coder:]... Custom instantiated controller must call -[super initWithCoder:]
Note that both my custom view controller class (which works) and my custom window controller class MainWindowController (which doesn't work) have implemented the trivial initializer:
required init?(coder: NSCoder) {
super.init(coder: coder)
}
I know that this functionality is new as of OS 10.15, but the documentation says it should work for window controllers AND view controllers, and the error message does not make any sense to me.

I hit the same problem, I thought about it a bit and here is how I worked around it.
First, why do I need this for ? I wanted to inject some dependencies to my view controller hierarchy before it's built from the Storyboard. I guess that's what the API is intended to.
But then, would that method be working, how would I pass the injection information down the view controller hierarchy ?
So, as the method is working without bug for view controllers, I decided to inject the information directly at the root view controller.
So, I have in my storyboard :
A window controller scene named "my-window-controller", which window just points to an empty view controller.
A view controller scene named "root-view-controller", where all the view hierarchy is described.
And wherever I want to create that view controller, I just do :
func instanciateWindowController(storyboard: NSStoryboard) -> NSWindowController {
// Load the (empty) window controller scene
let wcSceneIdentifier = NSStoryboard.SceneIdentifier("my-window-controller")
let windowController = storyboard.instantiateController(withIdentifier: wcSceneIdentifier)
as! NSWindowController
// Load the root view controller using the creator trick to inject dependencies
let vcSceneIdentifier = NSStoryboard.SceneIdentifier("root-view-controller")
let viewController = storyboard.instantiateController(identifier: vcSceneIdentifier,
creator: { coder in
return MyOwnViewController.init(coder: coder,
text: "Victoire !") // just pass here your injection info
})
// Associate the window controller and the root view controller
windowController.contentViewController = viewController
return windowController
}
with
class MyOwnViewController: MSViewController {
init?(coder: NSCoder,
text: String) { // receive here the injection information
print(text) // use the injection information here
super.init(coder: coder)
}
// Not used, but required
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}

This is filed as Feedback #FB7626059, if you’d like to pile on (I hit the issue too).

Related

Failed to instantiate the default view controller for UIMainStoryboardFile

I am writing a small sample app with Xamarin and MvvmCross 6.4.2. I completed the Xamarin.Android version and am now starting the Xamarin.iOS version. I created a view controller for the first screen:
public class SignInViewController : MvxViewController<SignInViewModel>
{
public SignInViewController() : base(nameof(SignInViewController), null)
{
}
public override void ViewDidLoad()
{
// never gets called...
base.ViewDidLoad();
}
}
When I run the app, I just get a blank screen and ViewDidLoad never gets called. In the application output it says:
Failed to instantiate the default view controller for
UIMainStoryboardFile 'Main' - perhaps the designated entry point is
not set?
My Main.storyboard is blank and I tried to modify it in Xcode Interface Builder to set my SignInViewController as the entry point, but I couldn't figure out how.
Failed to instantiate the default view controller for
UIMainStoryboardFile 'Main' - perhaps the designated entry point is
not set?
This error happens due to a simple mistake in your storyboard. When your app starts, iOS needs to know precisely which view controller needs to be shown first – known as your default view controller.
To fix it, add a ViewController to your Main.Storyboard and set it as Inital View Controller.
Refer: how-to-fix-the-error-failed-to-instantiate-the-default-view-controller-for-uimainstoryboardfile
And there are two SignInViewController in your project.
Use this one:
public partial class SignInViewController : UIViewController
{
public SignInViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
// never gets called...
base.ViewDidLoad();
View.BackgroundColor = UIColor.Red;
}
}
Update:

Handling NSMenuDelegate menuWillOpen for changing targets

There are lots of related answers about using menuWillOpen. They all explain that one needs to set the menu's delegate first.
This is easy when I have just one target, like a Preferences window or the main application.
But what if I have a document based app, and I need to have the active document handle menuWillOpen? Then the delegate isn't a constant any more.
What's the proper way to handle this? Do I have to set the delegate to a single object (like the AppDelegate) and then forward the call to the active view controller (but how is that done correctly)? Or is there some other elegant way?
I came up with this code which appears to work:
// This is in my AppDelegate class, and the NSMenu's delegate points to it:
- (void)menuWillOpen:(NSMenu *)menu {
// Forward to active document controller
NSWindow *mainWindow = [NSApplication sharedApplication].mainWindow;
NSResponder *r = mainWindow.firstResponder;
while (r) {
if ([r respondsToSelector:_cmd]) {
[(id<NSMenuDelegate>)r menuWillOpen:menu];
return;
}
r = r.nextResponder;
}
}
It assumes that a controller down the responder chain implements menuWillOpen:

OS X Core Data - Passing a Managed Object Context to a View Controller

I am developing a Mac Application in Xcode 7.3.1. and I am trying to pass a Model Object Context from my AppDelegate to an ArrayController.
I have a class named DataController which creates my Core Data stack. DataController.managedObjectContext holds the Managed Object Context.
My AppDelegate class is as follows:
class AppDelegate: NSObject, NSApplicationDelegate {
var dataController: DataController!
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
// Create an instance of the DataController class.
dataController = DataController()
// Create a reference to the first ViewController embedded in the WindowController.
guard let splitViewController = NSApplication.sharedApplication().windows[0].contentViewController as? ManagedObjectContextSettable
else { fatalError("Wrong view controller type")}
// Set the managedObjectContext property.
splitViewController.managedObjectContext = dataController.managedObjectContext
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
In my storyboard I have embedded a SplitViewController in my WindowController. The SplitViewController has its own custom View Controller class named SplitViewController. Here is the code in the SplitViewController:
class SplitViewController: NSSplitViewController, ManagedObjectContextSettable {
var managedObjectContext: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
// Create a reference to the first ViewController embedded in the WindowController.
let childControllers = self.childViewControllers
print("childControllers.count = \(childControllers.count)")
for childController in childControllers{
if childController.isKindOfClass(TableViewController){
print("Found TableViewController")
guard let tableViewController = childController as? ManagedObjectContextSettable
else { fatalError("Wrong view controller type")}
tableViewController.managedObjectContext = managedObjectContext
}
}
}
}
Within one of the Split View Items is my TableView which has its own View Controller named TableViewController. Here is the code for TableViewController:
class TableViewController: NSViewController, ManagedObjectContextSettable, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var tableView: NSTableView!
var managedObjectContext: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
//print(managedObjectContext.description)
}
}
In the storyboard I dragged an ArrayController and in the Bindings tab of the Inspector I have set Bind To and selected TableViewController and set the Model Key Path to 'self.managedObjectContext'. Ultimately it's not receiving the Managed Object Context.
I cannot establish if I should override the prepareForSegue function for an embedded ViewController, every example I read is for IOS.
Where am I going wrong please?
applicationDidFinishLaunching can be executed after viewDidLoad. Set managedObjectContext of the childControllers when managedObjectContext of the splitViewController is set.
Bindings use KVO. Change var managedObjectContext to dynamic var managedObjectContext to make the property KVO compliant.
If you are using an array controller with Cocoa bindings you have to override the init(coder:) method and initialize the managed context there to perform the implicit initial fetch. viewDidLoad is too late.
The segue workflow is the same as in iOS. It's even more convenient because there is a property presentingViewController to get the reference to the parent view controller.
The Core Data Manager DataController is supposed to be a singleton to ensure that the managed object context instance is always the same.
This may not be good practice, but it appears to work.
I made my DataController class a singleton class to ensure there was only one Managed Object Context. In my TableViewController I created a managedObjectContext property as follows:
lazy var managedObjectContext = DataController.sharedInstance.managedObjectContext
In my array controller I bind the managed object context parameter to the TableViewController and set the Model Key Path to self.managedObjectContext.
Can this be improved upon?

NSWindowController in Swift. Subclassing and initializing with Nib

In a test Swift project, I am subclassing NSWindowController. My NSWindowController subclass is designed to work with a particular Nib file. It is desirable, then, that when my window controller is initialized, the nib file is automatically loaded by the window controller instance. In Objective-C, this was achieved by doing:
#implementation MyWindowController
- (id)init {
self = [super initWithWindowNibName:"MyWindowNib"]
if (self) {
// whatever
}
return self
}
#end
Now, in Swift this is not possible: init() cannot call super.init(windowNibName:), because the later is declared not as a designated initializer, but as a convenience one by NSWindowController.
How can this be done in Swift? I don't see a strightforward way of doing it.
P.S.: I have seen other questions regarding this topic, but, as long as I've been able to understand, the solutions all point to initialize the Window Controller by calling init(windowNibName:). Please note that this is not the desired beheaviour. The Window Controller should be initialized with init(), and it should be the Window Controller itself who "picks up" its Nib file and loads it.
If you use the init() just to call super.init(windowNibName:), you could instead just override the windowNibName variable.
override var windowNibName: String {
get {
return "MyWindowNib"
}
}
Then there should be no need to mess with the initializers.
You can create your own convenience initializer instead:
override convenience init() {
self.init(windowNibName: "MyWindowNib")
}
You should instead opt in to replacing all designated initializers in your subclass, simply delegating to super where appropriate. Confer https://stackoverflow.com/a/24220904/1460929

Swift + Xcode 6 beta 3 + Core Data = awakeFromInsert not called?

Need help.
I'm creating new Document-based Core Data Cocoa project.
Add entity named 'Entity' into the core data model. Add 'creationDate' propery into it and set its type as Date. And create NSManagedObject subclass from 'Editor' menu.
Now I add into 'Entity.swift' file this code:
override func awakeFromInsert() {
super.awakeFromInsert()
self.creationDate = NSDate()
println("awakeFromInsert called")
}
Now in my NSPersistentDocument subclass I write such a init() method:
init() {
super.init()
var context = self.managedObjectContext
context.undoManager.disableUndoRegistration()
var entity = NSEntityDescription.insertNewObjectForEntityForName("Entity", inManagedObjectContext: context)
context.processPendingChanges()
context.undoManager.enableUndoRegistration()
println("\(entity)")
}
Everything compiles... BUT awakeFromInsert is never called! The interesting part is that 'entity' object ain't nil! It was created, but not initialized. And if I write this line in init method
entity.creationDate = NSDate()
then creationDate property will be set to a current date as expected.
But that's not all. If I debug execution step-by-step I can see that execution enters 'Entity.swift' file, but starts from the top of the file, then immediately drops and returns back to the NSPersistentDocument subclass file.
Tell me, is it a bug? Because I'm tired to fight with this nonsense. Thanks.
Accidentally I got it work: you have to add #objc(YourSubclass) before subclass declaration. I usually did #objc class MySubclass and turned out it does not work (don't know why).
WORKING:
#objc(YourSubclass)
class YourSubclass : NSManagedObject {
...
NOT WORKING:
#objc class YourSubclass : NSManagedObject {
...

Resources