Подтвердить что ты не робот

Подкласс NSWindowController в Swift и init (windowNibName)

Я пытаюсь запустить новый проект на основе Cocoa в Swift и хочу создать подкласс NSWindowController (как рекомендовано в руководствах Apple в приложениях на основе документов). В ObjC вы должны сделать экземпляр подкласса NSWindowController, отправив сообщение initWithWindowNibName:, которое было соответствующим образом реализовано, вызывая метод суперклассов.

В Swift init(windowNibName) доступен только как инициализатор удобства, назначенный инициализатор NSWindowController равен init(window), который, очевидно, хочет, чтобы я проходил в окне.

Я не могу вызывать super.init(windowNibName) из моего подкласса, потому что это не назначенный инициализатор, поэтому я, очевидно, должен реализовать convenience init(windowNibName), который, в свою очередь, должен вызвать self.init(window). Но если все, что у меня есть, это мой файл nib, как мне получить доступ к окну файла nib для отправки на этот инициализатор?

4b9b3361

Ответ 1

Вам нужно переопределить либо все три назначенных инициализатора NSWindowController (init(), init(window) и init(coder)), либо ни один из них, и в этом случае ваш подкласс автоматически наследует init(windowNibName) и все остальные удобства инициализаторы, и вы сможете его построить, используя инициализатор удобства суперкласса:

// 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")

Это описано в разделе "Инициализация/Автоматическое наследование инициализатора" в руководстве по языку:

Однако инициализаторы суперкласса автоматически наследуются, если выполняются определенные условия. На практике это означает, что вам не нужно писать переопределения инициализатора во многих распространенных сценариях и может наследовать инициализаторы суперкласса с минимальными усилиями, когда это безопасно.

Предполагая, что вы предоставляете значения по умолчанию для любых новых свойств, которые вы вводите в подкласс, применяются следующие два правила:

Правило 1Если ваш подкласс не определяет какие-либо назначенные инициализаторы, он автоматически наследует все его инициализаторы, назначенные суперклассам.

Правило 2Если ваш подкласс обеспечивает реализацию всех инициализаторов, назначенных суперклассам, либо путем наследования их в соответствии с правилом 1, либо путем предоставления пользовательской реализации как части определения, то он автоматически наследует все инициализаторы удобства суперкласса.

Ответ 2

Вместо того, чтобы переопределять любой из методов init, вы можете просто переопределить свойство windowNibName и вернуть стробированную строку. Это позволяет вам вызвать базовый метод инициализации ванили для создания оконного контроллера.

class WindowController: NSWindowController {

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

let windowController = WindowController()

Я предпочитаю это при вызове let windowController = WindowController(windowNibName: "NameOfNib"), так как имя nib является деталью реализации, которая должна быть полностью инкапсулирована в класс контроллера окон и никогда не экспонироваться снаружи (и просто проще вызвать WindowController()).

Если вы хотите добавить дополнительные параметры в метод init, выполните следующие действия:

  • В вашем пользовательском вызове метода init super.init(window: nil). Это приведет к NSWindowController для инициализации с помощью свойства windowNibName.
  • Отмените требуемый метод init(coder: NSCoder) для настройки вашего объекта или просто вызовите fatalError(), чтобы запретить его использование (albiet во время выполнения).
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)

Ответ 3

Мне удалось обойти это, просто используя метод класса, который вызывает инициализатор удобства, изменяет пользовательские переменные, а затем возвращает новый объект.

Итак, метод Objective C init будет выглядеть следующим образом:

//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

Вот как я сделал версию 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

Гром гениальности в ответе @hamstergene заключается в том, чтобы переопределить init(), который наследуется от NSResponder. Теперь можно ввести новый инициализатор и делегировать self.init(windowNibName: NoteWindowName), который, в свою очередь, унаследован после всех трех обозначенных инициализаторов переопределен:

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")
    }
}

Теперь больше не нужно указывать настраиваемому оконному контроллеру, с какого файла загружать nib. Вместо этого он может быть специализирован для любого мотивированного подкласса в первую очередь (например, для участия в некоторой иерархии документов)...

Ответ 5

Обновление ответа на hamstergene.

Это отлично работает на Xcode Version 6.1 (6A1052d)

Add your custom class to window controller

//
//  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 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__)
    }

}

Выход консоли:

(…/MainWindowController.swift, init(coder:))
(…/MainWindowController.swift, windowDidLoad())