基于Storyboard的AppKit应用中@IBSegueAction与NSSplitViewController使用问题
1. @IBSegueAction能否用于NSWindowController→NSSplitViewController的关系segue?
可以,但仅支持显式创建的segue,无法作用于Storyboard自动关联的contentViewController关系。如果NSWindowController是初始控制器,且SplitViewController是其默认内容控制器,AppKit会直接调用默认init(coder:),不会触发@IBSegueAction。
要生效需:
- 删除NSWindowController与SplitViewController的自动关联
- 手动从NSWindowController拖到SplitViewController创建segue,设置唯一标识符
- 在NSWindowController中实现@IBSegueAction方法,并将segue绑定到该方法
不过这种方式不如instantiateInitialController(creator:)稳定,后者更适合顶层控制器的依赖注入。
2. 能否用于NSSplitViewController的子视图控制器?
可以,但同样需要用显式segue替代默认的split view item关联。NSSplitViewController默认的子控制器自动关联属于Storyboard内置逻辑,不会触发@IBSegueAction。
可行操作:
- 删除SplitViewController与子控制器的自动关联
- 从SplitViewController拖到子控制器创建segue并设置标识符
- 在SplitViewController中实现@IBSegueAction方法,手动初始化子控制器并注入依赖
- 在
viewDidLoad中将子控制器添加到arrangedViewControllers数组
更简洁的方案是在SplitViewController初始化时手动创建子控制器,无需依赖Storyboard segue。
3. instantiateInitialController(creator:)在基于Storyboard的AppKit NSSplitViewController层级中能否正常工作?
完全可以,这是当前AppKit中最可靠的顶层控制器依赖注入方案,能彻底绕过默认init(coder:)调用。
核心逻辑:启动时通过instantiateInitialController(creator:)手动初始化NSWindowController,在闭包内创建所有依赖控制器(含SplitViewController及其子控制器),注入依赖后完成关联。
示例代码:
// 应用启动入口(如AppDelegate) func applicationDidFinishLaunching(_ aNotification: Notification) { let storyboard = NSStoryboard(name: "Main", bundle: nil) let dependency = Dependency() // 你的依赖实例 guard let windowController = storyboard.instantiateInitialController(ofType: NSWindowController.self) { coder in // 初始化SplitViewController let splitVC = SplitViewController(coder: coder, dependency: dependency)! // 初始化子控制器(若子控制器在Storyboard中,可改用instantiateController(withIdentifier:creator:)) let sidebarVC = SidebarViewController(coder: NSKeyedUnarchiver.empty, dependency: dependency)! let detailVC = DetailViewController(coder: NSKeyedUnarchiver.empty, dependency: dependency)! // 关联到SplitViewController splitVC.splitViewItems = [ NSSplitViewItem(viewController: sidebarVC), NSSplitViewItem(viewController: detailVC) ] // 返回配置完成的WindowController return NSWindowController(coder: coder, contentViewController: splitVC) } else { windowController.showWindow(nil) } }
4. 基于Storyboard的AppKit拆分视图控制器应用有推荐的依赖注入模式吗?
推荐两种实用模式,按需选择:
模式一:初始化时注入(Initializer Injection)
最符合SOLID原则,依赖关系明确,无运行时空值风险。
- 给所有需要依赖的控制器添加带参数的自定义初始化器(可保留
init(coder:)但标记为不可用,仅在creator闭包中调用) - 通过
instantiateInitialController(creator:)从顶层开始,手动创建所有控制器并注入依赖 - 子控制器由父控制器负责创建和关联,完全绕过Storyboard自动实例化逻辑
模式二:属性注入(Property Injection)
适合快速实现,无需修改初始化逻辑,但依赖关系不够直观。
- 保留所有控制器的默认
init(coder:)(不要抛出fatalError) - 给控制器添加隐式解包可选的依赖属性(如
var dependency: Dependency!) - 在顶层控制器(如NSWindowController)的
windowDidLoad方法中,遍历所有子控制器并注入依赖
示例代码:
// 所有需要依赖的控制器添加var dependency: Dependency!属性 class MainWindowController: NSWindowController { override func windowDidLoad() { super.windowDidLoad() let dependency = Dependency() guard let splitVC = contentViewController as? SplitViewController else { return } splitVC.dependency = dependency splitVC.splitViewItems.forEach { item in if let sidebar = item.viewController as? SidebarViewController { sidebar.dependency = dependency } else if let detail = item.viewController as? DetailViewController { detail.dependency = dependency } } } }
内容的提问来源于stack exchange,提问作者user10711707

