Я пытаюсь выполнить этот макет
Если я попробую HStack, завернутый в VStack, я получу это:
Если я попробую VStack, завернутый в HStack, я получу следующее:
Есть ли способ выровнять текст по базовой линии с текстовым полем и получить стандартный интервал от самой длинной метки до начала выровненных текстовых полей?
Да, я видел эту часть видео, но я не думаю, что она выполняет то, что мне нужно. Спасибо!
безумие, что это непросто, учитывая, насколько просто это делается в UIKit





Похоже, это сработает:
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)
}
}
}
}
С помощью этого кода я могу добиться такого макета:
Предостережение здесь заключается в том, что я должен был указать максимальную ширину для TextFields. Оставленная без ограничений, система компоновки, описанная в выступлении на WWDC, ссылку на которое я дал в комментариях, извлекает размер для TextFieldпрежний для выравнивания, в результате чего TextField для электронной почты выходит за пределы двух других. Я не уверен, как решить эту проблему таким образом, чтобы позволить TextField расширяться до размера содержащего представления, не переходя...
Использование пользовательской направляющей выравнивания смещает положение всего HStack, поэтому вам пришлось выровнять метки по правому краю, чтобы все выглядело нормально. Но это не тот макет, который мне нужен. Спасибо!
Это явно не тот макет, о котором просили.
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)
}
}
Отрегулируйте ширину рамки, чтобы они могли занимать больше места. Я не уверен, как это будет выглядеть на больших экранах.
Проблема с этим решением заключается в том, что вам нужно решить для maxWidth для всех перестановок размера экрана и размера текста (включая локализации и динамический тип).
Вы можете использовать хак для чтения геометрииконтики для этого:
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 имеют одинаковую высоту. Если ваши три текстовых поля имеют разную высоту или имеют появляющиеся/исчезающие метки проверки, которые влияют на отдельные высоты, вы можете использовать тот же метод, но вместо этого с тремя разными привязками высоты.
Поскольку это решение всегда будет сначала отображать текстовые поля без меток. Во время этой фазы рендеринга он установит высоту текстовых меток и вызовет другой рендеринг. Было бы более идеально рендерить все на одном этапе макета.
это может быть хак, но он кажется единственным, который не требует констант и фактически предоставляет запрошенный макет.
я не эксперт здесь, но мне удалось добиться желаемого макета, (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.
По какой-то причине правая сторона VStack использует интервал по умолчанию - может быть, 8 точек, но левая сторона VStack имеет интервал 0. Когда я добавляю интервал, выравнивание текста по отношению к TextField становится намного ближе.
Чтобы добавить к этому, я не думаю, что это дает вам желаемый эффект, если текстовое поле имеет высоту более одной строки. Центр левой метки НЕ будет выровнен по центру правой метки с этим решением.
@tetotechy сообщает, что ссылка на веб-сайт не найдена.
Вам нужно добавить фиксированную ширину и выравнивание по интерлиньяжу. Я тестировал в 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. См. дополнительную информацию здесь
При ближайшем рассмотрении Texts и TextFields вы можете заметить, что они имеют разную высоту, и это влияет на положение Texts относительно TextFields, как вы можете видеть в правой части скриншота, что Password Text выше относительно Password TextField, чем Username Text относительно Username TextField.
Я дал три способа решения этой проблемы здесь
Проблема с установкой ширины текста на 100 заключается в том, что она не гибкая. Если у вас более длинный текст (для переводов) или более крупный шрифт (с динамическим шрифтом), 100 может быть недостаточно. Принятый ответ выше решает для них. Спасибо, что нашли время ответить!
Я добавил случай, когда вы используете разные VStack для Text и TextField
Если вам нужна гибкая ширина, проверьте здесь
Этот ответ Geometry Reader просто устанавливает ширину на 1/3 от родителя. Это не учитывает более длинный текст, динамический тип и разные устройства. Принятый выше ответ достаточно близок с настройками и является самым простым решением, которое я нашел. Спасибо.
@Jerry Я проверял, и даже с самым маленьким экраном 100 идеально подходит. На самом деле, даже этот .extraExtraExtraLarge и iPhone SE (самый маленький экран) шириной 100 справляются со своей задачей.
@LorisFoe это может быть для текста, который у вас есть, но вы не знаете, какой длины будет текст при переводе на другие языки. Он может быть намного короче или намного длиннее. Решение должно быть достаточно гибким, чтобы работать с различной длиной.
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. — это важно, чтобы выравнивание начиналось
На днях я пытался сделать очень похожую вещь со SwiftUI. В докладе WWDC Создание пользовательских представлений с помощью SwiftUI рассказывается о нестандартном поведении выравнивания, которое вы, возможно, сможете использовать (посмотрите на 21-минутную отметку). Я мог бы представить себе создание пользовательского руководства по выравниванию вместе с
HStack, завернутым вVStack, чтобы получить желаемое выравнивание.