代码之家  ›  专栏  ›  技术社区  ›  superpuccio Kasra

SwiftUI:代码重用与视图组合

  •  0
  • superpuccio Kasra  · 技术社区  · 5 年前

    MyTextField . 用户界面可能是:

    enter image description here

    代码如下:

    struct MyTextField: View {
        @Binding var text: String
        var label: String
    
        var body: some View {
            VStack {
                HStack {
                    Text(label)
                    Spacer()
                }
                TextField("", text: $text) //here we have a simple TextField
                Divider()
            }
            .padding()
        }
    }
    

    现在,假设我们希望有另一个具有相同UI的textfield,但要在安全上下文中使用。此文本字段被调用 MySecureTextField SecureField 而不是 TextField ,但显然我不想这样创建一个全新的视图:

    struct MySecureTextField: View {
        @Binding var text: String
        var label: String
    
        var body: some View {
            VStack {
                HStack {
                    Text(label)
                    Spacer()
                }
                SecureField("", text: $text) //this time we have a SecureField here
                Divider()
            }
            .padding()
        }
    }
    

    我怎么能设计这样的情况?我尝试了几种方法,但都不正确:

    要使用以实际文本字段为参数的容器视图排序,请执行以下操作:

    struct TextFieldContainer<ActualTextField>: View where ActualTextField: View {
        private let actualTextField: () -> ActualTextField
        var label: String
    
        init(label: String, @ViewBuilder actualTextField: @escaping () -> ActualTextField) {
            self.label = label
            self.actualTextField = actualTextField
        }
    
        var body: some View {
            VStack {
                HStack {
                    Text(label)
                    Spacer()
                }
                actualTextField()
                Divider()
            }
            .padding()
        }
    }
    

    我可以用 TextFieldContainer 这种方式:

    struct ContentView: View {
        @State private var text = ""
    
        var body: some View {
            TextFieldContainer(label: "Label") {
                SecureField("", text: self.$text)
            }
        }
    }
    

    我的文本域 MySecureTextField ). 这样我甚至可以在容器中注入任何类型的视图,而不仅仅是文本字段。

    2秒尝试 要有一个私有容器和两个内部使用该容器的公共视图,请执行以下操作:

    private struct TextFieldContainer<ActualTextField>: View where ActualTextField: View {
        //...
        //the same implementation as above
        //...
    }
    
    struct MyTextField: View {
        @Binding var text: String //duplicated code (see MySecureTextField)
        let label: String //duplicated code (see MySecureTextField)
    
        var body: some View {
            TextFieldContainer(label: label) {
                TextField("", text: self.$text)
            }
        }
    }
    
    struct MySecureTextField: View {
        @Binding var text: String //duplicated code (see MyTextField)
        let label: String //duplicated code (see MyTextField)
    
        var body: some View {
            TextFieldContainer(label: label) {
                SecureField("", text: self.$text)
            }
        }
    }
    

    struct ContentView: View {
        @State private var text = ""
        @State private var text2 = ""
    
        var body: some View {
            VStack {
                MyTextField(text: $text, label: "Label")
                MySecureTextField(text: $text2, label: "Secure textfield")
            }
        }
    }
    

    我并不真的不喜欢这个解决方案,但是在属性上有一些代码重复。如果有很多属性,就会有很多代码重复。另外,如果我在 文本字段容器 我应该更改所有视图,因此,可能要更改很多结构( 我的文本域 , MySecureTextField , MyEmailTextField MyBlaBlaTextField ,等等)。

    3-我最后一次尝试 使用与中相同的方法 第二次尝试 AnyView 这种方式:

    struct MySecureTextField: View {
        private let content: AnyView
    
        init(text: Binding<String>, label: String) {
            content = AnyView(TextFieldContainer(label: label) {
                SecureField("", text: text)
            })
        }
    
        var body: some View {
            content
        }
    }
    
    struct MyTextField: View {
        private let content: AnyView
    
        init(text: Binding<String>, label: String) {
            content = AnyView(TextFieldContainer(label: label) {
                TextField("", text: text)
            })
        }
    
        var body: some View {
            content
        }
    }
    

    2 回复  |  直到 5 年前
        1
  •  4
  •   rob mayoff    5 年前

    您的第一次尝试是正确的方法,但不是让调用者提供文本字段,而是为不同的字段类型添加静态方法:

    struct TextFieldContainer<FieldView>: View where FieldView: View {
    
        var label: String
    
        var body: some View {
            VStack {
                HStack {
                    Text(label)
                    Spacer()
                }
                fieldView
                Divider()
            }
            .padding()
        }
    
        fileprivate init(label: String, fieldView: FieldView) {
            self.label = label
            self.fieldView = fieldView
        }
    
        private let fieldView: FieldView
    }
    
    extension TextFieldContainer where FieldView == TextField<Text> {
        static func plain(label: String, text: Binding<String>) -> some View {
            return Self(label: label, fieldView: TextField("", text: text))
        }
    }
    
    extension TextFieldContainer where FieldView == SecureField<Text> {
        static func secure(label: String, text: Binding<String>) -> some View {
            return Self(label: label, fieldView: SecureField("", text: text))
        }
    }
    

    示例使用:

    struct ContentView: View {
        @State private var text = ""
    
        var body: some View {
            VStack {
                TextFieldContainer.plain(label: "Label", text: $text)
                TextFieldContainer.secure(label: "Label", text: $text)
            }
        }
    }
    
        2
  •  7
  •   Quinn    5 年前

    你可以用一个简单的if!

    struct MyTextField: View {
        @Binding var text: String
        var label: String
        var secure: Bool = false
    
        var body: some View {
            VStack {
                HStack {
                    Text(label)
                    Spacer()
                }
    
                if secure {
                    SecureField("", text: $text)
                } else {
                    TextField("", text: $text)
                }
    
                Divider()
            }
            .padding()
        }
    }
    

    用法:

    MyTextField(text: $text, label: "Label") // unsecure
    MyTextField(text: $text, label: "Label", secure: true) // secure