Different Behavior When AppDelegate is Instantiated Programmatically?

I am working on a Cocoa Application where I do everything programmatically. I don't use the `@NSApplicationMain` macro - instead I have a main.swift file where I get the NSApplication shared instance and assign an instance of AppDelegate to be its delegate. I know that all of this is unnecessary. It is a personal learning exercise.


Right now, my application is very simple. I create a window and give it a content view. I put a scroll view inside the content view. I put a table inside the scroll view. I populate the table with arbitrary data (basically just strings numbered from 1 to 25).


All of that works fine, except that I can't select a table row, or edit any of the fields in the table. The row doesn't even highlight (turn blue) when I click on it. If I click on it twice, it still doesn't highlight, but I do get a focus ring around the textfield in the table, but can't enter any text.


If I do the exact same thing, only using @NSApplicationMain and instantiating AppDelegate in Interface Builder, I get the ability to select rows in the table and edit the table's textfields. Note that I still create the window and its views programmatically - all of the code in the application is exactly the same, except that the AppDelegate is instantiated in Interface Builder and @NSApplicationMain is used instead of my custom main.swift file.


So something is being configured in Interface Builder that is not configured when I create the AppDelegate programmatically. And what ever it is, it has to do with the ability of my table view or its text fields to become first responder, or to obtain a field editor, or something.


I could post code for all of this, but all of the code is exactly the same with the exception of substituting my main.swift file for @NSApplicationMain, and the only difference in behavior is the ability to select and edit fields in the table view.


Any ideas what it is that Interface Builder is configuring behind the scenes that makes it possible to edit textfields in the table view?

The code does seem important to me. What exactly is in your main.swift file for the case where you use one?


You never said whether you're using a storyboard or a NIB when you're instantiating in Interface Builder. Either way, presumably it also defines a main menu. For the case where you don't use a storyboard or NIB, are you creating a main menu? Also, does your Info.plist specify a main storyboard/NIB that isn't present for that case?

Here is the code for my main.swift file - as the code shows, I still use a "MainMenu" nib that loads the standard menu items in Interface Builder:


import Cocoa

private func setDelegate(
      application: NSApplication
    , delegate: NSApplicationDelegate? )

    -> NSApplication {
    
        application.delegate = delegate
        return application
}
private func runApplication(
      application: NSApplication        = NSApplication.sharedApplication()
    , delegate: NSApplicationDelegate?  = nil
    , bundle: NSBundle                  = NSBundle.mainBundle()
    , nibName: String                   = "MainMenu"
    , var topLevelObjects: NSArray?     = nil ) {
    
        if bundle.loadNibNamed(
              nibName
            , owner: setDelegate(application, delegate: delegate)
            , topLevelObjects: &topLevelObjects ) {
            
                application.run()
            
        } else {
        
            print("An error was encountered while starting the application.")
        
        }
}
runApplication(delegate: AppDelegate())


And here is my AppDelegate file:


import Cocoa

@NSApplicationMain       // This is commented out when I am using main.swift;
                         // That is the only difference in my AppDelegate file between the two scenarios

class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
    let mainWindow = MainWindow()

    func applicationDidFinishLaunching(notification: NSNotification) {
        mainWindow.makeKeyAndOrderFront(self)
    }
}


When I instantiate AppDelegate in Interface Builder I don't use a storyboard. I am using a nib. It is just one of the Blue Cube Objects from the object selector menu, dragged over to the object pane on the lefthand side of IB, with the delegate outlet connected to the File's Owner icon.


In either case, my info.plist file still specifies "MainMenu" as being the nib name that gets loaded when the application starts, since I use the "MainMenu" nib to configure the menus in either scenario.


So in scenario one:

1: I use main.swift and comment out @NSApplicationMain from my AppDelegate file

2: I delete the AppDelegate icon from the lefthand side of IB, so that the only nib icon that remains is the "Main Menu" icon (the File's Owner and First Responder icons are also still there.)


And in scenario two:

1: I remove the main.swift file from the application and uncomment @NSApplicationMain in the AppDelegate file

2: I leave the AppDelegate icon in Interface Builder and it is connected to the delegate outlet of the File's Owner icon.


Other than that, everything is exactly the same as far as I can tell.


Thanks for taking the time to respond, by the way...


[EDIT] Just for the sake of completeness, here is the code for my MainWindow and TableView classes - other than these two classes and the code above, there is no other code in the entire application:


import Cocoa

final class MainWindow: NSWindow, NSWindowDelegate {
    init() {
        let styleMask =
            NSResizableWindowMask
            | NSMiniaturizableWindowMask
            | NSClosableWindowMask
            | NSTitledWindowMask
       
        let backingStore = NSBackingStoreType.Buffered
       
        super.init(contentRect: NSMakeRect(320, 200, 640, 400), styleMask: styleMask, backing: backingStore, `defer`: true)
        delegate = self
       
        let view = NSView(frame: contentRectForFrameRect(frame))
        contentView = view
       
        let scrollView = NSScrollView()
        scrollView.translatesAutoresizingMaskIntoConstraints = false
       
        view.subviews = [ scrollView ]
       
        let viewDict =
            [ "scrollView": scrollView ]
       
        view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
              "H:|[scrollView(640)]|"
              , options: .AlignAllTop
              , metrics: nil
                , views: viewDict ) )
       
        view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
            "V:|[scrollView(400)]|"
            , options: .AlignAllLeading
            , metrics: nil
            , views: viewDict ) )
       
        let tableView = TableView()
        scrollView.documentView = tableView
       
    }
   
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
}

final class TableView: NSTableView, NSTableViewDelegate, NSTableViewDataSource {
    let data: [String] = {
        return (0..<25).map { "Item No. \($0)" }
    }()
   
    init() {
        super.init(frame: NSZeroRect)
        enabled = true
        let tableColumn = NSTableColumn(identifier: "mainColumn")
        tableColumn.headerCell.title = "Main Column"
        addTableColumn(tableColumn)
        setDelegate(self)
        setDataSource(self)
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
   
    func numberOfRowsInTableView(tableView: NSTableView) -> Int {
        return data.count
    }
   
    func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let view = NSTextField()
        view.editable = true
        view.selectable = true
        view.stringValue = data[row]
        view.drawsBackground = false
        view.bordered = false
        return view
    }
}

My suspicion is that the problem is with the memory management of the topLevelObjects array. The last use of topLevelObjects is in the

loadNibNamed(_:owner:topLevelObjects:) call. In theory, Swift/ARC could release it after that call completes and before the call to application.run().


Or possibly it's the memory management of the AppDelegate instance. NSApplication's delegate property is weak (as is typical for delegates). Again, the last strong reference to the delegate is the call to setDelegate(), and it may be released before application.run(). (In the case where the app delegate is instantiated in the NIB, it would end up in the top-level objects, which would be held strongly by the application object.)


Try putting dummy references to those variables (e.g. println() them) after application.run(). Such code will never be reached, but if that fixes the behavior of the app, then it explains the issue.

Accepted Answer

I solved it. 🙂


The problem was that my main.swift file was initializing my AppDelegate before NSApplication had been initialized. The Apple documentation makes it clear that lots of other Cocoa classes rely on NSApplication to be up and running when they are initialized. Apparently, NSObject and NSWindow are two of them.


So if you are doing this stuff programmatically, it is important to make sure that NSApplication has fully initialized before you run around creating other objects that may rely on it.


I rewrote my main.swift file so that the runApplication function doesn't initialize the AppDelegate until after NSApplication is fully initialized:


import Cocoa

// Changed the parameters so that the delegate parameter is a NSObject.Type
// parameter that can be initialized *after* NSApplication is fully initialized.
private func runApplication(
      application: NSApplication        = NSApplication.sharedApplication()
    , delegate: NSObject.Type?          = nil
    , bundle: NSBundle                  = NSBundle.mainBundle()
    , nibName: String                   = "MainMenu"
    , var topLevelObjects: NSArray?     = nil ) {
       
        // Actual initialization of the delegate is deferred until here:
        let del = delegate?()
       
        application.delegate = del as? NSApplicationDelegate
       
        if bundle.loadNibNamed(
            nibName
            , owner: application
            , topLevelObjects: &topLevelObjects ) {
           
            application.run()
               
        } else {
           
            print("An error was encountered while starting the application.")
           
        }
}

// Pass the delegate's Type as a parameter, instead of an instance of the
// class that has already been initialized:
runApplication(delegate: AppDelegate.self)
Different Behavior When AppDelegate is Instantiated Programmatically?
 
 
Q