使自定义 SwiftUI View 适应内置修饰符

Making a custom SwiftUI View adapt to built-in modifiers

我正在努力为 SwiftUI 编写自己的 BetterTextField 视图,因为内置的 TextField 在几个方面都缺乏。也就是说,我想支持延迟绑定(仅在定位焦点时更新绑定值,而不是在每次按键后强制重绘)、编程 focusing/responder 控制以及 UIKit UITextField 的一些 SwiftUI 缺乏的其他功能.

所以我创建了一个自定义的 UIViewRepresentable 并以协调器作为 UITextFieldDelegate,并且工作正常。但是,为了与其他视图保持一致,我真的想让我的自定义文本字段适应某些现有的 SwiftUI 修饰符。

例如:

// Here's my content view
struct ContentView: View {
    var body: some View {
        BetterTextField("Username", text: $username)
            // I want to adapt the view to this modifier
            .textFieldStyle(RoundedBorderTextFieldStyle())
    }
}

// Here's my (simplified) custom text field view
struct BetterTextField: UIViewRepresentable {
    var title: String
    @Binding var text: String

    init(_ title: String, text: Binding<String>) {
        self.title = title
        self._text = text
    }

    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField()
        textField.placeholder = title
        return textField
    }

    func updateUIView(_ view: UITextField, context: Context) {
        view.text = text

        // How can I check for the .textFieldStyle() modifier here and set the corresponding UIKit style accordingly?
        view.borderStyle = .roundedRect
    }
}

如评论所述,如何调整 UITextFieldborderStyle 属性 以匹配视图修饰符?

更一般地说,如何检查是否存在修饰符和 return 样式适当的自定义视图(例如 .bold() 可能转换为属性文本)?

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension View {

    /// Sets the style for `TextField` within the environment of `self`.
    public func textFieldStyle<S>(_ style: S) -> some View where S : TextFieldStyle

}

见备注

self

的环境中设置 TextField 的样式

UIViewRepresentable 继承自 View 但在 'self'

中没有任何 TextField

.bold, .italic ... 是 Font 的修饰符,而不是通用 View 的修饰符。假设

Image("image001").italic()

效果不佳。

去抖动见Debounced Property Wrapper

'delayed' 绑定见

/// Creates an instance with a `Text` label generated from a localized title
    /// string.
    ///
    /// - Parameters:
    ///     - titleKey: The key for the localized title of `self`, describing
    ///       its purpose.
    ///     - text: The text to be displayed and edited.
    ///     - onEditingChanged: An `Action` that will be called when the user
    ///     begins editing `text` and after the user finishes editing `text`,
    ///     passing a `Bool` indicating whether `self` is currently being edited
    ///     or not.
    ///     - onCommit: The action to perform when the user performs an action
    ///     (usually the return key) while the `TextField` has focus.
    public init(_ titleKey: LocalizedStringKey, text: Binding<String>, onEditingChanged: @escaping (Bool) -> Void = { _ in }, onCommit: @escaping () -> Void = {})

'delayed' 绑定示例

import SwiftUI
struct MyTextField<S>: View  where S: StringProtocol {
    let label: S
    @State private var __text = ""
    @Binding var text: String
    var body: some View {
        TextField(label, text: $__text, onEditingChanged: { (e) in

        }) {
            self.text = self.__text
        }
    }
}

struct ContentView: View {
    @State var text = " "
    var body: some View {
        VStack {
            MyTextField(label: "label", text: $text).textFieldStyle(RoundedBorderTextFieldStyle())
            Text(text)
        }.padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

如果您需要不同的字体和.bold,请使用

MyTextField(label: "label", text: $text).textFieldStyle(RoundedBorderTextFieldStyle()).font(Font.title.bold())

MyTextField(label: "label", text: $text).font(Font.title.bold()).textFieldStyle(RoundedBorderTextFieldStyle())

View 修饰符只是 return 又 some View 的函数,因此您可以实现对任何修饰符的支持,符合您认为适合您的自定义类型的任何协议。您的控件在每个已实现的修改器上的行为取决于您。

下面是对 textFieldStyle 修饰符的简单演示支​​持,它使您的 ContentView 渲染 BetterTextField 取决于添加或删除的圆形矩形样式修饰符。

struct BetterTextField: UIViewRepresentable {
    var title: String
    @Binding var text: String

    private let textField = UITextField()

    init(_ title: String, text: Binding<String>) {
        self.title = title
        self._text = text
    }

    func makeUIView(context: Context) -> UITextField {
        textField.placeholder = title
        return textField
    }

    func updateUIView(_ view: UITextField, context: Context) {
        view.text = text
    }
}

extension BetterTextField {
    func textFieldStyle<S>(_ style: S) -> some View where S : TextFieldStyle {
        if style is RoundedBorderTextFieldStyle {
            self.textField.borderStyle = .roundedRect
        }
        return self
    }
}