如何将SwiftUI按钮关联到NSView的Action方法?
解决SwiftUI按钮控制NSViewRepresentable内部方法的问题
我来帮你搞定这个问题~在SwiftUI里直接引用NSViewRepresentable内部的NSView实例确实行不通,因为SwiftUI是声明式UI框架,我们不能像传统AppKit那样直接持有视图对象并调用方法。正确的做法是通过状态绑定来驱动UI变化,让SwiftUI自动同步状态到底层的NSView。
下面是具体的修改方案,分步骤来:
1. 在ContentView中添加状态管理
首先在ContentView里用@State定义绘图类型的状态,按钮的动作只需要切换这个状态即可:
import SwiftUI struct ContentView : View { // 用@State管理当前绘图类型,默认是矩形 @State private var drawingType: DrawingType = .Rect var body: some View { VStack { HStack { Text("Hello") Image("LineTool") Button(action: { // 按钮点击时切换状态 drawingType = drawingType == .Rect ? .Circle : .Rect }) { Image("CenterCircleTool") } } // 将状态绑定传递给DrawingView DrawingView(drawingType: $drawingType) } } }
2. 修改DrawingView接收绑定状态
让DrawingView接收外部的@Binding状态,在updateNSView方法里把状态同步给底层的DrawingViewImplementation:
import SwiftUI // 让DrawingView接收外部的绑定状态 public struct DrawingView: NSViewRepresentable { @Binding var drawingType: DrawingType public typealias NSViewType = DrawingViewImplementation public func makeNSView(context: NSViewRepresentableContext<DrawingView>) -> DrawingViewImplementation { return DrawingViewImplementation() } public func updateNSView(_ nsView: DrawingViewImplementation, context: NSViewRepresentableContext<DrawingView>) { // 把SwiftUI的状态同步给NSView实例 nsView.currentType = drawingType // 触发重绘 nsView.setNeedsDisplay(nsView.bounds) } } enum DrawingType { case Rect case Circle }
3. 调整DrawingViewImplementation适配状态同步
原来的toggleDrawingType方法可以保留(比如用于鼠标点击事件),但现在主要通过状态同步来更新绘图类型:
public class DrawingViewImplementation: NSView { var currentType = DrawingType.Rect override public func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) NSColor.blue.set() switch currentType { case .Rect: NSRect(x: 100, y: 100, width: 100, height: 100).frame() case .Circle: NSBezierPath(ovalIn: NSRect(x: 100, y: 100, width: 100, height: 100)).stroke() } } // 保留这个方法用于鼠标点击触发切换 private func toggleDrawingType() { currentType = currentType == .Rect ? .Circle : .Rect setNeedsDisplay(bounds) } public override func mouseDown(with event: NSEvent) { toggleDrawingType() } }
(可选)双向状态同步
如果需要让鼠标点击DrawingView时,SwiftUI的状态也同步更新(比如按钮的状态要和当前绘图类型匹配),可以通过Coordinator实现双向绑定:
修改DrawingView添加Coordinator
public struct DrawingView: NSViewRepresentable { @Binding var drawingType: DrawingType // 创建Coordinator用于NSView和SwiftUI之间的通信 public func makeCoordinator() -> Coordinator { Coordinator(self) } public class Coordinator { var parent: DrawingView init(_ parent: DrawingView) { self.parent = parent } } public func makeNSView(context: NSViewRepresentableContext<DrawingView>) -> DrawingViewImplementation { let view = DrawingViewImplementation() // 把Coordinator传递给NSView view.coordinator = context.coordinator return view } public func updateNSView(_ nsView: DrawingViewImplementation, context: NSViewRepresentableContext<DrawingView>) { nsView.currentType = drawingType nsView.setNeedsDisplay(nsView.bounds) } }
修改DrawingViewImplementation通过Coordinator同步状态
public class DrawingViewImplementation: NSView { weak var coordinator: DrawingView.Coordinator? var currentType = DrawingType.Rect override public func draw(_ dirtyRect: NSRect) { // 绘图逻辑不变 super.draw(dirtyRect) NSColor.blue.set() switch currentType { case .Rect: NSRect(x: 100, y: 100, width: 100, height: 100).frame() case .Circle: NSBezierPath(ovalIn: NSRect(x: 100, y: 100, width: 100, height: 100)).stroke() } } public override func mouseDown(with event: NSEvent) { // 通过Coordinator修改SwiftUI的状态,实现双向同步 coordinator?.parent.drawingType = currentType == .Rect ? .Circle : .Rect } }
这样不管是点击SwiftUI按钮,还是点击DrawingView本身,状态都会保持一致,UI也会正确更新。
内容的提问来源于stack exchange,提问作者Matt Brandt
相关产品推荐
相关产品推荐

