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

Posted

技术标签:

【中文标题】使自定义 SwiftUI 视图适应内置修饰符【英文标题】:Making a custom SwiftUI View adapt to built-in modifiers 【发布时间】: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() 可能会转换为属性文本)?

【问题讨论】:

【参考方案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())

【讨论】:

以上是关于使自定义 SwiftUI 视图适应内置修饰符的主要内容,如果未能解决你的问题,请参考以下文章

java成神之——注释修饰符

SwiftUI-自定义修饰符

表单修饰符自定义指令计算属性侦听器过滤器生命周期

表单修饰符自定义指令计算属性侦听器过滤器生命周期

在 SwiftUI 的自定义视图中访问图像的修饰符

SwiftUI:自定义视图修饰符不符合 ViewModifier?