NSViewController参考周期与故事板

时间:2014-11-10 03:58:23

标签: macos swift osx-yosemite appkit

NSViewController,当从Swift下的故事板实例化时,似乎在某个地方有一个参考周期。

多次调用以下代码将实例化并设置新的视图控制器,但旧的视图控制器永远不会被释放。在代码中,containerViewController是一个NSViewController,它应该包含一个NSViewController,containerViewcontainerViewController中的子视图,identifier是要实例化的故事板标识符。

// Remove any sub viewcontrollers and their views
for viewController in containerViewController.childViewControllers as [NSViewController] {
    viewController.view.removeFromSuperview()
    viewController.removeFromParentViewController()
}
// Create and set up the new view controller and view.
let viewController = storyboard!.instantiateControllerWithIdentifier(identifier) as NSViewController
let view = viewController.view
view.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(viewController.view)
containerViewController.addChildViewController(viewController)
containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: nil, metrics: nil, views: ["view": view]))
containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: nil, metrics: nil, views: ["view": view]))

(示例项目不再可用)

我使用了Apple TSI并且他们认为这是我提交的一个错误,但是我希望其他人能够反对这一点,因为现在看来NSViewControllers和故事板现在是OSX上的事实。你是如何解决这个问题的?或者它不影响其他人,我做错了什么?

Pre-bounty edit:每个视图控制器必须能够从代码链接到任何其他视图控制器,因为目标是即时确定的。这似乎将segues作为一种选择。

错误修复

从Xcode 6.3开始,这不再是一个错误。

2 个答案:

答案 0 :(得分:3)

另一个答案。

看来,只有在Storyboard 上定义的 segues才能执行视图控制器释放。 所以,这是一个非常丑陋但有效的解决方法。

screenshot

class DismissSegue: NSStoryboardSegue {

    var nextViewControllerIdentifier:String?

    override func perform() {
        let src = self.sourceController as NSViewController
        let windowController = src.view.window!.windowController() as TopLevelWindowController

        src.view.removeFromSuperview()
        src.removeFromParentViewController()

        if let identifier = nextViewControllerIdentifier {
            windowController.setNewViewController(identifier)
        }
    }
}

class TopLevelWindowController: NSWindowController {

    var containerView: NSView!
    var containerViewController: ContainerViewController! {
        didSet {
            setNewViewController("FirstView")
        }
    }

    func setNewViewController(identifier: String) {
        // Create and set up the new view controller and view.
        let viewController = storyboard!.instantiateControllerWithIdentifier(identifier) as NSViewController
        let view = viewController.view
        view.translatesAutoresizingMaskIntoConstraints = false
        containerView.addSubview(viewController.view)
        containerViewController.addChildViewController(viewController)
        containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: nil, metrics: nil, views: ["view": view]))
        containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: nil, metrics: nil, views: ["view": view]))
    }
}

class ContainerViewController: NSViewController {

    @IBOutlet var containerView: NSView!

    override func viewDidAppear() {
        super.viewDidAppear()
        if let window = view.window {
            if let topLevelWindowController = window.windowController() as? TopLevelWindowController {
                topLevelWindowController.containerView = containerView
                topLevelWindowController.containerViewController = self
            }
        }
    }

}

class FirstViewController: NSViewController {

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
        NSLog("First VC init at \(pointerAddress)")
    }

    deinit {
        let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
        NSLog("First VC de-init at \(pointerAddress)")
    }

    override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject?) {
        if let segue = segue as? DismissSegue {
            segue.nextViewControllerIdentifier = "SecondView"
        }
    }
}

class SecondViewController: NSViewController {

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
        NSLog("Second VC init at \(pointerAddress)")
    }

    deinit {
        let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
        NSLog("Second VC de-init at \(pointerAddress)")
    }

    override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject?) {
        if let segue = segue as? DismissSegue {
            segue.nextViewControllerIdentifier = "FirstView"
        }
    }
}

修改故事板的步骤:

  1. 从按钮上断开@IBAction
  2. 创建" DUMMY"查看具有正常NSViewController的控制器场景。
  3. 将按钮中的自定义segue连接到" DUMMY"。
  4. 如图所示配置这些segue。

  5. 如果此解决方案无法满足您的需求,请与我们联系。

答案 1 :(得分:2)

这可能无法解决您的问题,但我找到的唯一解决方法是:

  • 使用"容器视图"
  • 嵌入初始视图控制器
  • @IBAction
  • 使用自定义NSStoryboardSegue在视图控制器之间切换。

这样的事情:

screenshot

import Cocoa

class TopLevelWindowController: NSWindowController {
}
class ContainerViewController: NSViewController {
}

class FirstViewController: NSViewController {
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
        NSLog("First VC init at \(pointerAddress)")
    }
    deinit {
        let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
        NSLog("First VC de-init at \(pointerAddress)")
    }
}

class SecondViewController: NSViewController {
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
        NSLog("Second VC init at \(pointerAddress)")
    }
    deinit {
        let pointerAddress = NSString(format: "%p", unsafeBitCast(self, Int.self))
        NSLog("Second VC de-init at \(pointerAddress)")
    }
}

class MySegue: NSStoryboardSegue {
    override func perform() {

        let source = self.sourceController as NSViewController
        let destination = self.destinationController as NSViewController

        if let containerViewController = source.parentViewController {

            source.view.removeFromSuperview()
            source.removeFromParentViewController()

            let view = destination.view
            let containerView = containerViewController.view

            view.translatesAutoresizingMaskIntoConstraints = false
            containerView.addSubview(view)
            containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: nil, metrics: nil, views: ["view": view]))
            containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: nil, metrics: nil, views: ["view": view]))
            containerViewController.addChildViewController(destination)
        }

    }
}