【问题标题】:Making a custom SwiftUI View adapt to built-in modifiers使自定义 SwiftUI 视图适应内置修饰符
【发布时间】:2020-03-06 06:18:45
【问题描述】:

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

因此,我创建了一个自定义 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 属性以匹配 View 修饰符?

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

【问题讨论】:

    标签: uikit uitextfield swiftui


    【解决方案1】:

    View 修饰符只是再次返回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
        }
    }
    

    【讨论】:

    • 是的,但是如果 RoundedBorderTextFieldStyle() 定义的不仅仅是圆角矩形,该怎么办?每次 Apple 改变它,你都必须重写你的代码。最好为此使用自定义修饰符(不同的命名)并且没有 TextFieldStyle 参数!
    • 只是好奇,你为什么把func textFieldStyle&lt;S&gt;写成扩展名?据我所知,它在功能上是相同的,包括在原始结构中。
    • @Extragorey,它只是一个界面设计样式,如果你愿意,最好的实践,通过扩展来分隔不同的功能区域,Apple 广泛使用它,如果我没记错的话,推荐一些 WWDC 会议。如果从技术上讲,那么是的,你是对的。
    • 这个答案对我有用,但重要的是.textFieldStyle 是视图中BetterTextField() 声明之后的第一个修饰符,否则使用Apple 的实现而不是这个(我还更改了返回键入从some ViewBetterTextField 以允许链接其他自定义修饰符)。
    • @Extragorey 为了不必依赖自定义修饰符的顺序,您可以使用可以通过上下文 (UIViewRepresentable.Context.environment) 访问的 EnvironmentValues。这样,您可以添加使用 .environment() 修饰符和自定义环境键 (EnvironmentKey) 的 View 扩展方法。每当该环境值发生变化时,updateUIView 就会被调用。
    【解决方案2】:
    @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 ... 是字体的修饰符,而不是通用视图的修饰符。说吧

    Image("image001").italic()
    

    也不行。

    去抖见Debounced Property Wrapper

    对于“延迟”绑定,请参阅

    /// 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 = {})
    

    “延迟”绑定示例

    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())
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-12
      • 1970-01-01
      相关资源
      最近更新 更多