为什么这个 SwiftUI 按钮扩展初始化程序不起作用?

How come this SwiftUI Button extension initializer doesn't work?

我正在尝试在 Button 上创建一个扩展,以在使用系统映像创建它们时减少样板文件。

extension Button {
  init(_ systemName: String, action: @escaping () -> Void) {
    self.init {
      action()
    } label: {
      Image(systemName: systemName) // <-- Error
    }
  }
}

这会导致以下编译器错误:

Cannot convert value of type 'Image' to closure result type 'Label'

查看 Button 的初始化程序,我看到它是这样声明的:

struct Button<Label> : View where Label : View {
  init(action: @escaping () -> Void, @ViewBuilder label: () -> Label) { ... }
}

但是类似地指定我的扩展名也不起作用。我哪里错了?

extension Button where Label : View {

你需要指定泛型类型,比如

extension Button where Label == Image {   // << here !!
  init(_ systemName: String, action: @escaping () -> Void) {
    self.init {
      action()
    } label: {
      Image(systemName: systemName) // << No Error !!
    }
  }
}

测试 Xcode 12.5 / iOS 14.5

注意:有关 Text 标签的系统扩展如何声明的详细信息,请参阅

extension Button where Label == Text {
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension Button where Label == Text {
    public init(_ titleKey: LocalizedStringKey, action: @escaping () -> Void)

您的方法有两个紧迫的问题:

  1. 您需要指定您的 Label 类型为 Image
  2. 您使用的签名将与 Button (Button(_ title: StringProtocol, action: () -> Void))
  3. 的现有构造函数冲突

你可以用你的扩展来解决这两个问题:

extension Button where Label == Image {
  init(systemName: String, action: @escaping () -> Void) { 
    self.init(action: action, label: { Image(systemName: systemName) })
  }
}

不过感觉更符合SwiftUI的API做自己的Button包装器:

struct SystemImageButton: View {
  let action: () -> Void
  let systemName: String

  init(systemName: String,
       action: @escaping () -> Void) {
    self.action = action
    self.systemName = systemName
  }

  var body: some View {
    Button(action: action,
           label: { Image(systemName: systemName) })
  }
}