You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:26:47