Макет страницы входа в SwiftUI - PullRequest
0 голосов
/ 17 июня 2019

Я изучаю SwiftUI, так как пытаюсь создать вид входа в систему, и теперь я столкнулся с проблемой

Вот чего я пытаюсь достичь:

What I already achieved

Как видите, я уже достиг этой точки, но мне не нравится моя реализация

struct ContentView : View {
@State var username: String = ""
var body: some View {
    VStack(alignment: .leading) {
        Text("Login")
            .font(.title)
            .multilineTextAlignment(.center)
            .lineLimit(nil)
            Text("Please")
                .font(.subheadline)

        HStack {
            VStack (alignment: .leading, spacing: 20) {
                Text("Username: ")
                Text("Password: ")

            }
            VStack {
                TextField($username, placeholder: Text("type something here..."))
                .textFieldStyle(.roundedBorder)
                TextField($username, placeholder: Text("type something here..."))
                    .textFieldStyle(.roundedBorder)
                }
            }
        }.padding()
    }
}

Поскольку для того, чтобы текст имени пользователя и пароля был выровнен точно по центру текстового поля, мне пришлось поместить буквенное значение интервала 20 в VStack, что мне не нравится, потому что, скорее всего, оно победило ' Работа на устройствах разных размеров.

Кто-нибудь видит лучший способ достичь того же результата?

Спасибо

Ответы [ 2 ]

3 голосов
/ 17 июня 2019

Вы можете использовать Spacer s вместе с модификатором fixedSize для высоты. Вы должны установить заданные высоты объекта любой строки, чтобы получить точное представление table style:

struct ContentView : View {

    private let height: Length = 32

    @State var username: String = ""
    var body: some View {
        VStack(alignment: .leading) {
            Text("Login")
                .font(.title)
                .multilineTextAlignment(.center)
                .lineLimit(nil)
            Text("Please")
                .font(.subheadline)

            HStack {
                VStack (alignment: .leading) {
                    Text("Username: ") .frame(height: height)
                    Spacer()
                    Text("Password: ") .frame(height: height)
                }
                VStack {
                    TextField($username, placeholder: Text("type something here..."))
                        .textFieldStyle(.roundedBorder)
                        .frame(height: height)
                    Spacer()
                    TextField($username, placeholder: Text("type something here..."))
                        .textFieldStyle(.roundedBorder)
                        .frame(height: height)
                }
                }
                .fixedSize(horizontal: false, vertical: true)

            }
            .padding()
    }
}

Обратите внимание, что установка высоты на TextField не влияет непосредственно на ее высоту, но просто устанавливает высоту высоты содержимого содержимого.

2 голосов
/ 19 июня 2019

Мы собираемся реализовать два новых метода модификатора View, чтобы мы могли написать это:

struct ContentView: View {
    @State var labelWidth: CGFloat? = nil
    @State var username = ""
    @State var password = ""

    var body: some View {
        VStack {
            HStack {
                Text("User:")
                    .equalSizedLabel(width: labelWidth, alignment: .trailing)
                TextField("User", text: $username)
            }
            HStack {
                Text("Password:")
                    .equalSizedLabel(width: labelWidth, alignment: .trailing)
                SecureField("Password", text: $password)
            }
        }
        .padding()
        .textFieldStyle(.roundedBorder)
        .storeMaxLabelWidth(in: $labelWidth)
    }
}

Два новых модификатора: equalSizedLabel(width:alignment:) и storeMaxLabelWidth(in:).

Модификатор equalSizedLabel(width:alignment) делает две вещи:

  1. Он применяет width и alignment к своему содержимому (представления Text(“User:”) и Text(“Password:”)).
  2. Он измеряет ширину своего содержимого и передает его любому представлению предка, которое этого хочет.

Модификатор storeMaxLabelWidth(in:) получает ширину, измеренную equalSizedLabel, и сохраняет максимальную ширину в привязке $labelWidth, которую мы передаем ей.

Итак, как мы реализуем эти модификаторы? Как мы передаем значение от представления потомка до предка? В SwiftUI мы делаем это, используя (в настоящее время недокументированную) систему «предпочтений».

Чтобы определить новое предпочтение, мы определяем тип, соответствующий PreferenceKey. Чтобы соответствовать PreferenceKey, мы должны определить значение по умолчанию для наших предпочтений, и мы должны определить, как объединить предпочтения нескольких подпредставлений. Мы хотим, чтобы нашим предпочтением была максимальная ширина всех меток, поэтому значение по умолчанию равно нулю, и мы объединяем предпочтения, беря максимум. Вот PreferenceKey мы будем использовать:

struct MaxLabelWidth: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value = max(value, nextValue())
    }
}

Функция модификатора preference устанавливает предпочтение, поэтому мы можем сказать .preference(key: MaxLabelWidth.self, value: width), чтобы установить наше предпочтение, но мы должны знать, что width установить. Нам нужно использовать GeometryReader, чтобы получить ширину, и это немного сложно сделать правильно, поэтому мы завернем это в ViewModifier следующим образом:

extension MaxLabelWidth: ViewModifier {
    func body(content: Content) -> some View {
        return content
            .background(GeometryReader { proxy in
                Color.clear
                    .preference(key: Self.self, value: proxy.size.width)
            })
    }
}

То, что происходит выше, это то, что мы прикрепляем фон View к контенту, потому что фон всегда имеет тот же размер, что и контент, к которому он прикреплен. Фон View - это GeometryReader, который (через proxy) предоставляет доступ к своему собственному размеру. Мы должны предоставить GeometryReader свой собственный контент. Поскольку мы на самом деле не хотим показывать фон за исходным контентом, мы используем Color.clear в качестве контента GeometryReader. Наконец, мы используем модификатор preference, чтобы сохранить ширину как предпочтение MaxLabelWidth.

Теперь можно определить методы модификатора equalSizedLabel(width:alignment:) и storeMaxLabelWidth(in:):

extension View {
    func equalSizedLabel(width: CGFloat?, alignment: Alignment) -> some View {
        return self
            .modifier(MaxLabelWidth())
            .frame(width: width, alignment: alignment)
    }
}

extension View {
    func storeMaxLabelWidth(in binding: Binding<CGFloat?>) -> some View {
        return self.onPreferenceChange(MaxLabelWidth.self) {
            binding.value = $0
        }
    }
}

Вот результат:

result screenshot

...