Вёрстка в SwiftUI с горизонтальным и вертикальным выравниванием

Я пытаюсь выполнить этот макет

Вёрстка в SwiftUI с горизонтальным и вертикальным выравниванием

Если я попробую HStack, завернутый в VStack, я получу это:

Вёрстка в SwiftUI с горизонтальным и вертикальным выравниванием

Если я попробую VStack, завернутый в HStack, я получу следующее:

Вёрстка в SwiftUI с горизонтальным и вертикальным выравниванием

Есть ли способ выровнять текст по базовой линии с текстовым полем и получить стандартный интервал от самой длинной метки до начала выровненных текстовых полей?

На днях я пытался сделать очень похожую вещь со SwiftUI. В докладе WWDC Создание пользовательских представлений с помощью SwiftUI рассказывается о нестандартном поведении выравнивания, которое вы, возможно, сможете использовать (посмотрите на 21-минутную отметку). Я мог бы представить себе создание пользовательского руководства по выравниванию вместе с HStack, завернутым в VStack, чтобы получить желаемое выравнивание.

Jumhyn 16.06.2019 03:31

Да, я видел эту часть видео, но я не думаю, что она выполняет то, что мне нужно. Спасибо!

Jerry 25.06.2019 17:42

безумие, что это непросто, учитывая, насколько просто это делается в UIKit

Jonathan. 17.11.2021 18:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
20
3
30 776
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Похоже, это сработает:

extension HorizontalAlignment {
    private enum MyAlignment: AlignmentID {
        static func defaultValue(in context: ViewDimensions) -> Length {
            context[.trailing]
        }
    }
    static let myAlignmentGuide = HorizontalAlignment(MyAlignment.self)
}

struct ContentView : View {
    @State var username: String = ""
    @State var email: String = ""
    @State var password: String = ""

    var body: some View {
        VStack(alignment: .myAlignmentGuide) {
            HStack {
                Text("Username").alignmentGuide(.myAlignmentGuide, computeValue: { d in d[.trailing] })
                TextField($username)
                    .textFieldStyle(.roundedBorder)
                    .frame(maxWidth: 200)
            }
            HStack {
                Text("Email")
                    .alignmentGuide(.myAlignmentGuide, computeValue: { d in d[.trailing] })
                TextField($email)
                    .textFieldStyle(.roundedBorder)
                    .frame(maxWidth: 200)
            }
            HStack {
                Text("Password")
                    .alignmentGuide(.myAlignmentGuide, computeValue: { d in d[.trailing] })
                TextField($password)
                    .textFieldStyle(.roundedBorder)
                    .frame(maxWidth: 200)
            }
        }
    }
}

С помощью этого кода я могу добиться такого макета:

Aligned layout

Предостережение здесь заключается в том, что я должен был указать максимальную ширину для TextFields. Оставленная без ограничений, система компоновки, описанная в выступлении на WWDC, ссылку на которое я дал в комментариях, извлекает размер для TextFieldпрежний для выравнивания, в результате чего TextField для электронной почты выходит за пределы двух других. Я не уверен, как решить эту проблему таким образом, чтобы позволить TextField расширяться до размера содержащего представления, не переходя...

Использование пользовательской направляющей выравнивания смещает положение всего HStack, поэтому вам пришлось выровнять метки по правому краю, чтобы все выглядело нормально. Но это не тот макет, который мне нужен. Спасибо!

Jerry 25.06.2019 17:45

Это явно не тот макет, о котором просили.

Jonathan. 17.11.2021 18:20
    var body: some View {

    VStack {
        HStack {
            Text("Username")
            Spacer()
            TextField($username)
                .textFieldStyle(.roundedBorder)
                .frame(maxWidth: 200)
                .foregroundColor(.gray)
                .accentColor(.red)
        }
        .padding(.horizontal, 20)
        HStack {
            Text("Email")
            Spacer()
            TextField($email)
                .textFieldStyle(.roundedBorder)
                .frame(maxWidth: 200)
                .foregroundColor(.gray)
            }
            .padding(.horizontal, 20)
        HStack {
            Text("Password")
            Spacer()
            TextField($password)
                .textFieldStyle(.roundedBorder)
                .frame(maxWidth: 200)
                .foregroundColor(.gray)
            }
            .padding(.horizontal, 20)
    }
}

This is the results of the above code. It use an HStack wrapped in a VStack and has the alignment you are trying to achieve

Отрегулируйте ширину рамки, чтобы они могли занимать больше места. Я не уверен, как это будет выглядеть на больших экранах.

DLAN 19.06.2019 04:14

Проблема с этим решением заключается в том, что вам нужно решить для maxWidth для всех перестановок размера экрана и размера текста (включая локализации и динамический тип).

Jerry 25.06.2019 17:44

Вы можете использовать хак для чтения геометрииконтики для этого:

SwiftUI live preview

struct Column: View {
    @State private var height: CGFloat = 0
    @State var text = ""
    let spacing: CGFloat = 8

    var body: some View {
        HStack {
            VStack(alignment: .leading, spacing: spacing) {
                Group {
                    Text("Hello world")
                    Text("Hello Two")
                    Text("Hello Three")
                }.frame(height: height)
            }.fixedSize(horizontal: true, vertical: false)
            VStack(spacing: spacing) {
                TextField("label", text: $text).bindHeight(to: $height)
                TextField("label 2", text: $text)
                TextField("label 3", text: $text)
            }.textFieldStyle(RoundedBorderTextFieldStyle())
        }.fixedSize().padding()
    }
}

extension View {
    func bindHeight(to binding: Binding<CGFloat>) -> some View {
        func spacer(with geometry: GeometryProxy) -> some View {
            DispatchQueue.main.async { binding.value = geometry.size.height }
            return Spacer()
        }
        return background(GeometryReader(content: spacer))
    }
}

Здесь мы только считываем высоту первого TextField и применяем ее три раза к трем разным Text View, предполагая, что все TextField имеют одинаковую высоту. Если ваши три текстовых поля имеют разную высоту или имеют появляющиеся/исчезающие метки проверки, которые влияют на отдельные высоты, вы можете использовать тот же метод, но вместо этого с тремя разными привязками высоты.

Почему это немного взломано?

Поскольку это решение всегда будет сначала отображать текстовые поля без меток. Во время этой фазы рендеринга он установит высоту текстовых меток и вызовет другой рендеринг. Было бы более идеально рендерить все на одном этапе макета.

это может быть хак, но он кажется единственным, который не требует констант и фактически предоставляет запрошенный макет.

Jonathan. 17.11.2021 18:21
Ответ принят как подходящий

я не эксперт здесь, но мне удалось добиться желаемого макета, (1) выбрав альтернативу 2-VStacks-in-a-HStack, (2) обрамив внешние метки, (3) освободив их от ограничения вертикального расширения по умолчанию. назначая их maxHeight = .infinity и (4) фиксируя высоту HStack

struct ContentView: View {
    @State var text = ""
    let labels = ["Username", "Email", "Password"]

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                ForEach(labels, id: \.self) { label in
                    Text(label)
                        .frame(maxHeight: .infinity)
                        .padding(.bottom, 4)
                }
            }

            VStack {
                ForEach(labels, id: \.self) { label in
                    TextField(label, text: self.$text)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                }
            }
            .padding(.leading)
        }
        .padding(.horizontal)
        .fixedSize(horizontal: false, vertical: true)
    }
}

Вот результат превью:

чтобы учесть смещение базовых линий внешних и внутренних меток (сопутствующая проблема, не связанная с этим конкретным макетом — см., например, это обсуждение), я вручную добавил отступы

спасибо этот сайт за то, что просветил меня на пути к пониманию Хитрости макета SwiftUI

Это решение выглядит хорошо. Единственный «хак» — это заполнение, чтобы текст соответствовал TextField.

Jerry 26.09.2019 00:05

По какой-то причине правая сторона VStack использует интервал по умолчанию - может быть, 8 точек, но левая сторона VStack имеет интервал 0. Когда я добавляю интервал, выравнивание текста по отношению к TextField становится намного ближе.

Jerry 26.09.2019 00:13

Чтобы добавить к этому, я не думаю, что это дает вам желаемый эффект, если текстовое поле имеет высоту более одной строки. Центр левой метки НЕ будет выровнен по центру правой метки с этим решением.

Luis 08.04.2020 22:13

@tetotechy сообщает, что ссылка на веб-сайт не найдена.

andrewbuilder 14.11.2021 23:07

Вам нужно добавить фиксированную ширину и выравнивание по интерлиньяжу. Я тестировал в Xcode 11.1, все в порядке.

struct TextInputWithLabelDemo: View {
    @State var text = ""
    let labels = ["Username", "Email", "Password"]

    var body: some View {
        VStack {
            ForEach(labels, id: \.self) { label in
                HStack {
                    Text(label).frame(width: 100, alignment: .leading)
                    TextField(label, text: self.$text)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                }
            }
            .padding(.horizontal)
            .fixedSize(horizontal: false, vertical: true)
        }
    }
}

Ниже вы можете увидеть, в чем проблема, когда мы используем разные VStack для Text и TextField. См. дополнительную информацию здесь

Обновлено 16 октября 2019 г.

При ближайшем рассмотрении Texts и TextFields вы можете заметить, что они имеют разную высоту, и это влияет на положение Texts относительно TextFields, как вы можете видеть в правой части скриншота, что Password Text выше относительно Password TextField, чем Username Text относительно Username TextField. Я дал три способа решения этой проблемы здесь

Проблема с установкой ширины текста на 100 заключается в том, что она не гибкая. Если у вас более длинный текст (для переводов) или более крупный шрифт (с динамическим шрифтом), 100 может быть недостаточно. Принятый ответ выше решает для них. Спасибо, что нашли время ответить!

Jerry 15.10.2019 17:31

Я добавил случай, когда вы используете разные VStack для Text и TextField

Victor Kushnerov 15.10.2019 19:10

Если вам нужна гибкая ширина, проверьте здесь

Victor Kushnerov 15.10.2019 19:13

Этот ответ Geometry Reader просто устанавливает ширину на 1/3 от родителя. Это не учитывает более длинный текст, динамический тип и разные устройства. Принятый выше ответ достаточно близок с настройками и является самым простым решением, которое я нашел. Спасибо.

Jerry 15.10.2019 23:11

@Jerry Я проверял, и даже с самым маленьким экраном 100 идеально подходит. На самом деле, даже этот .extraExtraExtraLarge и iPhone SE (самый маленький экран) шириной 100 справляются со своей задачей.

LetsGoBrandon 27.08.2021 14:46

@LorisFoe это может быть для текста, который у вас есть, но вы не знаете, какой длины будет текст при переводе на другие языки. Он может быть намного короче или намного длиннее. Решение должно быть достаточно гибким, чтобы работать с различной длиной.

Jerry 27.08.2021 18:15
    HStack{
        Image(model.image)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 10, alignment: .leading)
        VStack(alignment: .leading) {
            Text("Second column ")
            Text("Second column -")
        }
        Spacer()
        Text("3rd column")
    }

1- первый столбец - изображение

2- вторая колонка - два текста

3- плавающее значение

Spacer () — поиграйте с Spacer () -> пример выше, и vstack остается вместе с выравниванием по вертикали для всех строк, просто поместите разделитель для представлений, которые вы хотите сделать, в другом вертикальном выравнивании / столбце. VStack(alignment: .leading. — это важно, чтобы выравнивание начиналось

Другие вопросы по теме