SwiftUI桥接协议事件回调Coordinator
SwiftUI桥接协议事件回调Coordinator

SwiftUI桥接协议事件回调Coordinator

Coordinator 是 SwiftUI 中 UIViewRepresentable(iOS)或 NSViewRepresentable(macOS)协议的一部分,用来桥接 SwiftUI 与 UIKit/AppKit 控件之间的事件回调。

核心作用:让传统控件(如 NSColorWell、NSSlider、UIButton)能通过 target-action 或 delegate,把事件传回 SwiftUI 的状态或逻辑中。

为什么需要Coordinator?

UIKit/AppKit 控件通常是这样响应用户操作的:

button.target = someObject
button.action = #selector(someMethod(_:))

但 SwiftUI 是声明式的,没有 target-action 概念,为了让两者交互,SwiftUI 提供了 Coordinator。

Coordinator 的标准使用场景

NSViewRepresentable为例,在SwiftUI中实现AppKit控件时,需要实现makeNSView和updateNSView方法。

比如,在SwiftUI中实现NSSlider:

struct NSSomeView: NSViewRepresentable {
    
    func makeNSView(context: Context) -> NSSlider {
        let slider = NSSlider()
        slider.doubleValue = 50
        slider.minValue = 0
        slider.maxValue = 100
        return slider
    }
    
    func updateNSView(_ nsView: NSSlider, context: Context) {
    }
}

在SwiftUI中可以通过NSSomeView()显示对应的NS控件。

但是如果想要通过回调获取到控件的值,就需要使用Coordinator:

func makeCoordinator() -> Coordinator

然后在里面定义事件处理逻辑,如果不使用Coordinator,就无法获取到NS控件值。

Coordinator使用实例

以使用场景中的NSSlider为例,如果想要获取到NSSlider的值,就需要绑定一个变量,并通过Coordinator实现值的回调:

struct MySlider: NSViewRepresentable {
    @Binding var value: Double
    
    func makeNSView(context: Context) -> NSSlider {
        let slider = NSSlider(value: value, minValue: 0, maxValue: 100,
                              target: context.coordinator,
                              action: #selector(Coordinator.valueChanged(_:)))
        return slider
    }
    
    func updateNSView(_ nsView: NSSlider, context: Context) {
        if nsView.doubleValue != value {
            nsView.doubleValue = value
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(value: $value)
    }
    
    class Coordinator: NSObject {
        var value: Binding<Double>
        
        init(value: Binding<Double>) {
            self.value = value
        }
        
        @objc func valueChanged(_ sender: NSSlider) {
            value.wrappedValue = sender.doubleValue
            print("值改变为:\(value.wrappedValue)")
        }
    }
}

在SwiftUI中,通过 @Binding 实现数据的传输绑定:

struct ContentView: View {
    @State private var value: Double = 50
    var body: some View {
        VStack {
            Text("进度:") + Text(String(format: "%.f",value)) + Text("%")
            MySlider(value: $value)
                .frame(width: 100,height:35)
        }
        .frame(width: 400,height:300)
    }
}

代码详解

1、结构体和 @Binding 变量

struct MySlider: NSViewRepresentable {
    @Binding var value: Double

首先,创建一个遵循NSViewRepresentable结构体,用于显示NS控件。

创建一个 @Binding 的变量,用于接收从 SwiftUI 父视图传递的数值,当用户滑动滑块时,将更新的值传回SwiftUI的变量。

2、makeNSView

func makeNSView(context: Context) -> NSSlider

这是SwiftUI创建NSSlider时,第一次调用的函数。

let slider = NSSlider(
    value: value,        // 初始值(来自 @Binding)
    minValue: 0,
    maxValue: 100,
    target: context.coordinator,    // 设置 target 为协调器
    action: #selector(Coordinator.valueChanged(_:))
)

创建了一个NSSlider控件,并设置初始化、最小值、最大值、target和action。

当用户滑动滑块时,会调用Coordinator的valueChanged方法。

3、updateNSView

func updateNSView(_ nsView: NSSlider, context: Context)

这是 SwiftUI 检测到 @State 或 @Binding 变化后会自动调用的更新函数,用来同步最新值到 NSSlider:

if nsView.doubleValue != value {
    nsView.doubleValue = value
}

如果隐藏updateNSView内部的代码,NSSlider控件仍然可以实现数据的回调,但是无法从SwiftUI状态同步到AppKit视图。

例如,在SwiftUI中创建一个生成随机数的按钮:

Button("修改随机值") {
    value = Double(Int.random(in: 0...100))
}

当点击该按钮时,外部的 @State 值就会改变,NSSlider控件就会通过 @Binding 检测到外部值发生了变化,SwiftUI自动调用updateNSView方法,将 NSSlider 的值同步到最新状态。

4、makeCoordinator

func makeCoordinator() -> Coordinator

SwiftUI 在构造视图时调用这个方法,用于创建一个事件回调桥梁对象。

自定义了一个 Coordinator 类:

Coordinator(value: $value)

把 @Binding 传进去,之后可以在 AppKit 控件里使用。

这里实际上是将 NSSlider 控件的变量传递到Coordinator类中,如果不实现Coordinator类,NSSlider控件的变量就无法传递给Coordinator类。

5、Coordinator(协调器)

class Coordinator: NSObject {
    var value: Binding<Double>
    
    init(value: Binding<Double>) {
        self.value = value
    }
    
    @objc func valueChanged(_ sender: NSSlider) {
        value.wrappedValue = sender.doubleValue
        print("值改变为:\(value.wrappedValue)")
    }
}

value: Binding<Double> 是通过makeCoordinator方法传递进来的,绑定的是 NSSlider 的 @Bindg变量,实际上用来回写 SwiftUI @State。

@objc func valueChanged 是 NSSlider 用户操作时调用的事件方法。

在这个方法中将 NSSlider滑块的值写回 @Binding。

Coordinator交互控件

1、NSColorWell:颜色变化回调传回 SwiftUI。

2、NSSlider:滑动数值更新 SwiftUI 的 @State。

3、NSTextField:编辑完成后更新 SwiftUI 的绑定。

4、NSView(自定义):手势、拖拽、鼠标事件等处理。

凡是需要数据回调的控件都需要用到Coordinator,只有展示或不需要交互的情况下,Coordinator才是可选的。

当需要 target-action、delegate、通知、代理等,就建议用 Coordinator。

Coordinator命名约定

如果在 NSViewRepresentable 中定义了一个类名叫做 Coordinator,SwiftUI 会强制要求实现 makeCoordinator() 方法。

struct MySlider: NSViewRepresentable {  // 报错
    // func makeCoordinator() -> Coordinator { }    // 没有实现 makeCoordinator
    class Coordinator: NSObject { }     // 名称为 Coordinator
}

如果将这个类命名为别的名字(如 SliderDelegate 或 SomeHelper),SwiftUI 就不会强制实现 makeCoordinator(),因为它“不知道”是否需要使用协调器。

struct MySlider: NSViewRepresentable {
    // func makeCoordinator() -> Coordinator { }    // 不报错
    class SliderDelegate: NSObject { }  // 其他名称
}

这里的命名约定是因为,SwiftUI 的 NSViewRepresentable 协议有个默认行为:

associatedtype Coordinator = Void

也就是说,如果不实现 makeCoordinator() 方法,它假设不需要协调器,就用默认的 Void。

但当声明了一个嵌套类型 Coordinator,SwiftUI 就会:

1、自动把写的 Coordinator 类识别为“协调器类型”,

2、要求实现:

func makeCoordinator() -> Coordinator

所以出现了这种“写了类就必须实现方法”的行为 —— 是协议和默认泛型的联动机制。

总结

Coordinator是连接 SwiftUI 和 AppKit 的桥梁,通过实现 makeCoordinator() 方法,返回自定义类实例。

主要用于控件的事件处理、状态同步、手势监听等

相关文章

1、SwiftUI显示AppKit视图的NSViewRepresentable协议:https://fangjunyu.com/2025/07/02/swiftui%e6%98%be%e7%a4%baappkit%e8%a7%86%e5%9b%be%e7%9a%84nsviewrepresentable%e5%8d%8f%e8%ae%ae/

2、Swift科普文《associatedtype》:https://fangjunyu.com/2024/10/21/swift%e7%a7%91%e6%99%ae%e6%96%87%e3%80%8aassociatedtype%e3%80%8b/

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注