SwiftUI 传递 ViewModifier 作为参数

7

我正在创建一个自定义的TextField视图,它由多个装饰视图组成。我希望能够设置内部的TextField视图修饰符,例如键盘、大写等,这些只适用于子视图。

我认为最好的做法不是为每个视图属性创建属性,而是传入一个可选的ViewModifier参数,并像这样使用它:

struct MySuperTextField: View {
    var vm: ViewModifier?

    var body: some View {
         TextField(...)
           .modifier( vm ?? EmptyModifier() )
         // ... more views here
    }
}

由于ViewModifier中的associatedType,所以这个不起作用。遗憾的是,也没有像AnyViewModifier这样的东西(我也想不出如何制作一个可行的)。

有人成功做到类似的事情吗?我在搜索网络时找不到任何相关信息。

以下是示例:

struct LastNameModifier: ViewModifier {
   func body(content: Content) -> some View {
       content
          .autocapitalization(.words)
          .textContentType(.familyName)
          .backgroundColor(.green)
          // ... anything else specific to names
   }
}

struct EmailModifier: ViewModifier {
   func body(content: Content) -> some View {
       content
          .keyboardType(.emailAddress)
          .textContentType(.emailAddress)
          .backgroundColor(.yellow)
          // ... anything else specific to emails
   }
}

然后像这样与我的 MySuperTextField 一起使用:

VStack {
    MySuperTextField("Last Name", $lastName, vm: LastNameModifier())

    MySuperTextField("Email", $email, vm: EmailModifier())
}

我不明白你试图解决什么问题。你能举个例子吗? - pawello2222
问题在于我有一个常见的装饰TextField,用于设置诸如姓名、电子邮件地址、电话号码等内容,每个内容都有自己的键盘和内容类型。因此,我想通过每次调用来设置这些内容。目前,我能够通过将其应用于整个视图来实现,因为与其他视图没有冲突。但是,我可以想象一些像背景颜色这样的东西,我只想将其应用于“内部”TextField,而不是完全装饰的视图。 - GilroyKilroy
似乎更有用的是拥有一个通用的表单对象,它接受一组可变输入的设置结构体。您可以在此处阅读有关多重泛型符合性的更多信息:https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID192 - Ryan
3个回答

8

如果我理解正确,您可以使您的MySuperTextField接受一个泛型参数:

struct MySuperTextField<V>: View where V: ViewModifier {
    private let placeholder: String
    @Binding private var text: String
    private let vm: V
    
    init(_ placeholder: String, text: Binding<String>, vm: V) {
        self.placeholder = placeholder
        self._text = text
        self.vm = vm
    }

    var body: some View {
        TextField(placeholder, text: $text)
            .modifier(vm)
    }
}

然后,您可以将某些ViewModifier作为参数传递:

struct ContentView: View {
    @State private var text: String = "Test"

    var body: some View {
        MySuperTextField("Last Name", text: $text, vm: LastNameModifier())
    }
}

如果你需要一种跳过在创建时使用的vm参数的方法:
MySuperTextField("Last Name", text: $text)

您可以创建一个扩展:

extension MySuperTextField where V == EmptyModifier {
    init(_ placeholder: String, text: Binding<String>) {
        self.placeholder = placeholder
        self._text = text
        self.vm = EmptyModifier()
    }
}

1

由于 ViewModifier 具有无法用作实例变量的关联类型,但我提出了另一种使用方式:

MySuperTextField 不必维护 ViewModifier 类型的实例。

struct MySuperTextField: View {
    @State var textValue = ""

    var body: some View {
        TextField("", text: $textValue)
    }
}

对于符合 ViewModifier 协议的每个结构,您需要创建一个扩展:

extension MySuperTextField {
    func lastNameModifier() -> some View {
        modifier(LastNameModifier())
    }

    func emailModifier() -> some View {
        modifier(EmailModifier())
    }
}

你可以这样使用它:

struct ContentView : View {
    var body: some View {
        VStack {
            MySuperTextField(textValue: "Last Name")
                .lastNameModifier()
            MySuperTextField(textValue: "Email")
                .emailModifier()
        }
    }
}

0

将ViewModifier设置为结构体,并在视图上调用它。您只需要明确指定类型,例如我使用的buttonStyle修饰符:

struct PurchaseButtonStyle: ButtonStyle {
    let geometry: GeometryProxy
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .frame(minWidth: 0, idealWidth: 300, maxWidth: .infinity)
            .padding(.all, geometry.size.height / 30)
            .foregroundColor(.black)
            .background(.orange)
            .cornerRadius(30)

    }
}

使用方法为:

Button(...)
    .buttonStyle(PurchaseButtonStyle(geometry: Geometry))

你只需要编写特定的修改器即可。我之所以使用它,是因为在我正在开发的应用程序中很方便。


这并不能解决我的问题,因为我想传递样式而不是在通用视图中硬编码它们。 - GilroyKilroy
如果你看一下,你会发现我传递了一个变量。你可以传递任意数量的变量来自定义样式。但是,你必须返回一个特定类型和仅限于特定类型。这就是你遇到问题的地方。对于我的情况,我只是试图为应用程序创建一个统一的按钮,但我可以根据需要随时更改它。 - Yrb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接