Subclase NSWindowController en Swift e init (windowNibName)


Estoy tratando de iniciar un nuevo proyecto Cocoa basado en documentos en Swift y quiero crear una subclase de NSWindowController (como se recomienda en las guías de Apple sobre aplicaciones basadas en documentos). En ObjC se haría una instancia de una subclase NSWindowController enviando el mensaje initWithWindowNibName:, que se implementó en consecuencia, llamando al método superclasses.

En Swift init(windowNibName) solo está disponible como inicializador de conveniencia, el inicializador designado de NSWindowController es init(window) que obviamente quiere que pase un ventana.

No puedo llamar a super.init(windowNibName) desde mi subclase, porque no es el inicializador designado, por lo que obviamente tengo que implementar convenience init(windowNibName), que a su vez necesita llamar self.init(window). Pero si todo lo que tengo es mi archivo nib, ¿cómo puedo acceder a la ventana del archivo nib para enviarlo a ese inicializador?

Author: Martin, 2014-06-14

5 answers

Debe anular los tres inicializadores designados de NSWindowController (init(), init(window) y init(coder)), o ninguno de ellos, en cuyo caso tu subclase heredará automáticamente init(windowNibName) y todos los demás inicializadores de conveniencia y podrás construirlo usando el inicializador de conveniencia de la superclase:

// this overrides none of designated initializers
class MyWindowController: NSWindowController {
    override func windowDidLoad() {
        super.windowDidLoad()
    }
}

// this one overrides all of them
//
// Awkwardly enough, I see only two initializers 
// when viewing `NSWindowController` source from Xcode, 
// but I have to also override `init()` to make these rules apply.
// Seems like a bug.
class MyWindowController: NSWindowController
{
    init()
    {
        super.init()
    }

    init(window: NSWindow!)
    {
        super.init(window: window)
    }

    init(coder: NSCoder!)
    {
        super.init(coder: coder)
    }

    override func windowDidLoad() {
        super.windowDidLoad()
    }
}

// this will work with either of the above
let mwc: MyWindowController! = MyWindowController(windowNibName: "MyWindow")

Esto está cubierto por "Inicialización / Herencia del Inicializador automático" en la guía del idioma:

Sin embargo, los inicializadores de superclase se heredan automáticamente si se cumplen ciertas condiciones. En la práctica, esto significa que no necesita escribir sobreescrituras de inicializadores en muchos escenarios comunes, y puede heredar sus inicializadores de superclase con un esfuerzo mínimo siempre que sea seguro hacerlo.

Suponiendo que proporcione valores predeterminados para cualquier propiedad nueva que introduzca en una subclase, se aplican las siguientes dos reglas:

Artículo 1 Si su subclase no define ningún inicializador designado, hereda automáticamente todos sus inicializadores designados superclase.

Artículo 2 Si su subclase proporciona una implementación de todos sus inicializadores designados como superclase, ya sea heredándolos según la regla 1 o proporcionando una implementación personalizada como parte de su definición, entonces hereda automáticamente todos los inicializadores de conveniencia de superclase.

 14
Author: hamstergene,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-14 14:36:51

En lugar de sobrescribir cualquiera de los métodos init, simplemente puede sobrescribir la propiedad windowNibName y devolver una cadena codificada. Esto le permite llamar al método básico vanilla init para crear el controlador de ventana.

class WindowController: NSWindowController {

    override var windowNibName: String! {
        return "NameOfNib"
    }
}

let windowController = WindowController()

Prefiero esto a llamar a let windowController = WindowController(windowNibName: "NameOfNib") ya que el nombre del nib es un detalle de implementación que debe estar completamente encapsulado dentro de la clase window controller y nunca expuesto fuera (y es simplemente más fácil llamar a WindowController()).

Si desea agregar los parámetros adicionales al método init hacen lo siguiente:

  • En su método init personalizado llame a super.init(window: nil). Esto hará que NSWindowController se inicie con la propiedad windowNibName.
  • Invalida el método requerido init(coder: NSCoder) para configurar tu objeto o simplemente llamar a fatalError() para prohibir su uso (albiet en tiempo de ejecución).
class WindowController: NSWindowController {

    var test: Bool

    override var windowNibName: String! {
        return "NameOfNib"
    }

    init(test: Bool) {
        self.test = test
        super.init(window: nil) // Call this to get NSWindowController to init with the windowNibName property
    }

    // Override this as required per the class spec
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented. Use init()")

        // OR

        self.test = false
        super.init(coder: coder)
    }
}

let windowController = WindowController(test: true)
 12
Author: ospr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-18 21:48:02

Pude solucionar esto simplemente teniendo un método de clase que llama al inicializador de conveniencia, modifica las variables personalizadas y luego devuelve el nuevo objeto.

Así que un método de Objective C init se vería así:

//Class.h
@class PPPlugInInfo;

@interface PPPlugInInfoController : NSWindowController
//...
- (id)initWithPlugInInfo:(PPPlugInInfo *)plugInfo;
//...
@end

//Class.m
#include "Class.h"

@interface PPPlugInInfoController ()
@property (strong) PPPlugInInfo *info;
@end

@implementation PPPlugInInfoController

- (id)initWithPlugInInfo:(PPPlugInInfo *)plugInfo;
{
    if (self = [self initWithWindowNibName:@"PPPlugInInfoController"]) {
        self.info = plugInfo;
    }
    return self;
}

@end

Así es como hice la versión Swift:

class PPPluginInfoController: NSWindowController {
    private var info: PPPlugInInfo!
    class func windowControllerFromInfo(plugInfo: PPPlugInInfo) -> Self {
        var toRet = self(windowNibName:"PPPlugInInfoController")
        toRet.info = plugInfo

        return toRet
    }
}
 4
Author: MaddTheSane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-11 22:11:50

El golpe de genio en la respuesta de @hamstergene es anular init() también, que se hereda de NSResponder. Ahora se puede introducir un nuevo inicializador y delegar en self.init(windowNibName: NoteWindowName), que a su vez se hereda una vez que los tres inicializadores designados se anulan:

class WindowController: NSWindowController {

    var note: Document! // must be optional because self is not available before delegating to designated init

    convenience init(note: Document) {
        self.init(windowNibName: NoteWindowName)
        self.document = document
    }

    override init(window: NSWindow?) {
        super.init(window: window)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override init() {
        fatalError("init() has not been implemented")
    }
}

Ahora ya no es necesario decirle al controlador de ventanas personalizado desde qué archivo nib cargar. En su lugar, se puede especializar para lo que motivó a la subclase en primer lugar (como participar en algunos jerarquía de documentos, por ejemplo)...

 1
Author: milos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-04 16:18:33

Una actualización de la respuesta de hamstergene.

Esto funciona bien en Xcode Versión 6.1 (6A1052d)

Agregue su clase personalizada al controlador de ventana

//
//  MainWindowController.swift
//  VHDA Editor
//
//  Created by Holyfield on 20/11/14.
//  Copyright (c) 2014 Holyfield. All rights reserved.
//

import Cocoa

class MainWindowController: NSWindowController {

    //override func windowDidLoad() {
    //    super.windowDidLoad()

        // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
   // }

    override init()
    {
        super.init()
        println(__FILE__, __FUNCTION__)
    }

    override init(window: NSWindow!)
    {
        super.init(window: window)
        println(__FILE__, __FUNCTION__)
    }

    required init?(coder: (NSCoder!))
    {
        super.init(coder: coder)
        println(__FILE__, __FUNCTION__)
    }

    override func windowDidLoad() {
        super.windowDidLoad()
        println(__FILE__, __FUNCTION__)
    }

}

Salida de consola:

(…/MainWindowController.swift, init(coder:))
(…/MainWindowController.swift, windowDidLoad())
 -1
Author: D.A.H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-20 14:57:17