SwiftUI转Cocoa:窗口无显示及跨视图变量修改问题咨询
问题解决:Cocoa视图间变量修改与窗口不显示问题
一、先修复窗口不显示及视图异常问题
你的代码存在几个关键错误,导致窗口内容无法正常显示:
- 在
draw(_:)方法中添加子视图:draw会在窗口重绘时频繁触发,每次调用都会重复添加按钮和CustomView2,造成视图层级混乱。子视图应该在视图初始化阶段创建并添加。 - 重复创建
CustomView2实例:AppDelegate中创建了一个customView2添加到窗口,同时CustomView1内部又创建了另一个实例,点击按钮修改的是内部不可见的实例,窗口上的标签自然不会变化。 - 子视图布局超出范围:
CustomView1内部的customView2位置设置不合理,导致其超出父视图可视范围,无法被看到。
二、视图间通信的正确实现(以闭包回调为例)
闭包是Swift中简单直接的视图通信方式,类似SwiftUI的回调逻辑。以下是修复后的完整代码:
main.swift(无需修改)
import Cocoa let app = NSApplication.shared let appDelegate = App2() app.delegate = appDelegate app.setActivationPolicy(.regular) app.activate(ignoringOtherApps:true) app.run()
App2.swift
import Cocoa class App2: NSObject, NSApplicationDelegate { var window: NSWindow! var customView1: CustomView1! var customView2: CustomView2! func buildMenu() { let mainMenu = NSMenu() NSApp.mainMenu = mainMenu let appMenuItem = NSMenuItem() mainMenu.addItem(appMenuItem) let appMenu = NSMenu() appMenuItem.submenu = appMenu appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q") } func buildWnd() { let _wndW: CGFloat = 400 let _wndH: CGFloat = 400 let appTitle: String = "app title" window = NSWindow(contentRect: NSMakeRect(0, 0, _wndW, _wndH), styleMask: [.titled, .closable, .miniaturizable, .resizable], backing: .buffered, defer: false) window.center() window.title = appTitle window.makeKeyAndOrderFront(window) // 创建两个自定义视图 customView2 = CustomView2(frame: NSRect(x: 10, y: 100, width: 120, height: 50)) customView1 = CustomView1(frame: NSRect(x: 10, y: 50, width: 120, height: 50)) // 设置按钮点击回调,修改CustomView2的标签内容 customView1.onButtonClick = { self.customView2.label.stringValue = "Button clicked!" } window.contentView?.addSubview(customView1) window.contentView?.addSubview(customView2) } class CustomView1: NSView { // 定义闭包作为点击回调 var onButtonClick: (() -> Void)? override init(frame frameRect: NSRect) { super.init(frame: frameRect) setupSubviews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { // 创建按钮并添加到视图 let button = NSButton(frame: NSRect(x: 10, y: 10, width: 100, height: 30)) button.title = "Click me!" button.bezelStyle = .rounded button.target = self button.action = #selector(buttonClicked(_:)) self.addSubview(button) } @objc func buttonClicked(_ sender: NSButton) { // 触发回调 onButtonClick?() } } class CustomView2: NSView { let label = NSTextField(frame: NSRect(x: 10, y: 10, width: 100, height: 20)) override init(frame frameRect: NSRect) { super.init(frame: frameRect) setupLabel() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupLabel() { label.stringValue = "Hello, world!" label.isEditable = false label.isSelectable = false label.isBezeled = false label.drawsBackground = false self.addSubview(label) } } func applicationDidFinishLaunching(_ notification: Notification) { buildMenu() buildWnd() } func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } }
三、其他常用的视图通信方式
1. 代理模式
适合复杂交互场景,通过协议定义交互规则:
protocol CustomView1Delegate: AnyObject { func buttonDidClick() } class CustomView1: NSView { weak var delegate: CustomView1Delegate? @objc func buttonClicked(_ sender: NSButton) { delegate?.buttonDidClick() } } // 在AppDelegate中实现代理 class App2: NSObject, NSApplicationDelegate, CustomView1Delegate { func buildWnd() { // ... customView1.delegate = self } func buttonDidClick() { customView2.label.stringValue = "Button clicked via delegate!" } }
2. 通知中心(NSNotificationCenter)
适合跨层级、多视图响应同一事件的场景:
// 在CustomView1中发送通知 @objc func buttonClicked(_ sender: NSButton) { NotificationCenter.default.post(name: NSNotification.Name("ButtonClicked"), object: nil) } // 在CustomView2中监听通知 class CustomView2: NSView { override init(frame frameRect: NSRect) { super.init(frame: frameRect) setupLabel() NotificationCenter.default.addObserver(self, selector: #selector(updateLabel), name: NSNotification.Name("ButtonClicked"), object: nil) } @objc private func updateLabel() { label.stringValue = "Button clicked via notification!" } deinit { NotificationCenter.default.removeObserver(self) } }
3. 绑定(NSBinding)
类似SwiftUI的状态管理,适用于KVC兼容的属性:
// 在CustomView2中绑定标签内容到属性 class CustomView2: NSView { @objc dynamic var labelText: String = "Hello, world!" override init(frame frameRect: NSRect) { super.init(frame: frameRect) setupLabel() label.bind(.value, to: self, withKeyPath: #keyPath(labelText), options: nil) } } // 在回调中修改属性,自动更新标签 customView1.onButtonClick = { self.customView2.labelText = "Button clicked via binding!" }
内容的提问来源于stack exchange,提问作者KanKonga
相关产品推荐
相关产品推荐

