簡體   English   中英

SwiftUI:代碼重用與視圖組合

[英]SwiftUI: code reuse with view composition

使用 SwiftUI 進行開發我發現很難一起重用代碼組合視圖。 我將向您展示一個簡單的示例:假設我們的應用中有一個帶有特定 UI 的文本字段。 讓我們將此文本字段稱為MyTextField 用戶界面可能是:

在此處輸入圖像描述

這是代碼:

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 的文本字段,但要在安全上下文中使用。 此文本字段稱為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()
    }
}

我該如何設計這樣的情況? 我嘗試了幾種方法,但沒有一種方法看起來是正確的:

1 - 第一次嘗試擁有一種將實際文本字段作為參數的容器視圖:

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

我不喜歡這個解決方案:我不想指定實際的文本字段,它應該隱含在視圖本身( MyTextFieldMySecureTextField )中。 這樣我什至可以在容器中注入任何類型的視圖,而不僅僅是一個文本字段。

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")
        }
    }
}

我並不是真的不喜歡這個解決方案,但是屬性上有一些代碼重復。 如果有很多屬性,就會有很多代碼重復。 此外,如果我更改了TextFieldContainer上的某些屬性,我應該因此更改所有視圖,可能需要更改很多結構( MyTextFieldMySecureTextFieldMyEmailTextFieldMyBlaBlaTextField等)。

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
    }
}

這與第二次嘗試沒有什么不同,我的直覺是我錯過了完成這項常見任務的正確方法(SwiftUI-y 方式)。 您能否指出正確的“設計模式”或改進我描述的解決方案之一? 對不起,很長的問題。

你可以使用一個簡單的 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 (self.secure) {
                SecureField("", text: $text)
            } else {
                TextField("", text: $text)
            }
            Divider()
        }
        .padding()
    }
}

用法:

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

您的第一次嘗試是正確的方法,但不是讓調用者提供文本字段,而是為不同的字段類型添加 static 方法:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM