Заставить VStack заполнить ширину экрана в SwiftUI

Учитывая этот код:

import SwiftUI

struct ContentView : View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("Title")
                .font(.title)

            Text("Content")
                .lineLimit(nil)
                .font(.body)

            Spacer()
        }
        .background(Color.red)
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

Это приводит к этому взаимодействию:

Как я могу сделать VStack заполнить ширину экрана, даже если меткам / текстовым компонентам не нужна полная ширина?

Уловка, которую я нашел, состоит в том, чтобы вставить пустой HStack в структуре вот так:

VStack(alignment: .leading) {
    HStack {
        Spacer()
    }
    Text("Title")
        .font(.title)

    Text("Content")
        .lineLimit(nil)
        .font(.body)

    Spacer()
}

Что дает желаемый дизайн:

Есть ли способ лучше?

19 ответов

Решение

Попробуйте использовать модификатор.frame со следующими параметрами:

.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: Alignment.topLeading)
    struct ContentView : View {
        var body: some View {
            VStack(alignment: .leading) {
                Text("Hello World")
                    .font(.title)
                Text("Another")
                    .font(.body)
                Spacer()
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
            .background(Color.red)
        }
    }

Это описывается как гибкая рамка ( см. Документацию), которая растягивается, чтобы заполнить весь экран, и когда у нее есть дополнительное пространство, она центрирует свое содержимое внутри него.

В Swift 5.2 и iOS 13.4, в соответствии с вашими потребностями, вы можете использовать один из следующих примеров для согласования вашего VStack с верхними ведущими ограничениями и полноразмерным фреймом.

Обратите внимание, что все приведенные ниже фрагменты кода приводят к одинаковому отображению, но не гарантируют эффективного кадра VStack ни количество View элементы, которые могут появиться при отладке иерархии представлений.


1. Использование frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) метод

Самый простой подход - установить рамку вашего VStack с максимальной шириной и высотой, а также пройти необходимое выравнивание в frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:):

struct ContentView: View {

    var body: some View {
        VStack(alignment: .leading) {
            Text("Title")
                .font(.title)
            Text("Content")
                .font(.body)
        }
        .frame(
            maxWidth: .infinity,
            maxHeight: .infinity,
            alignment: .topLeading
        )
        .background(Color.red)
    }

}

В качестве альтернативы, если установить максимальный кадр с определенным выравниванием для вашего Views - это общий шаблон в вашей кодовой базе, вы можете создать метод расширения на View для этого:

extension View {

    func fullSize(alignment: Alignment = .center) -> some View {
        self.frame(
            maxWidth: .infinity,
            maxHeight: .infinity,
            alignment: alignment
        )
    }

}

struct ContentView : View {

    var body: some View {
        VStack(alignment: .leading) {
            Text("Title")
                .font(.title)
            Text("Content")
                .font(.body)
        }
        .fullSize(alignment: .topLeading)
        .background(Color.red)
    }

}

2. Использование Spacers для принудительного выравнивания

Вы можете встроить свой VStack внутри в полный размер HStack и используйте трейлинг и дно Spacers, чтобы заставить ваш VStack верхнее выравнивание:

struct ContentView: View {

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("Title")
                    .font(.title)
                Text("Content")
                    .font(.body)

                Spacer() // VStack bottom spacer
            }

            Spacer() // HStack trailing spacer
        }
        .frame(
            maxWidth: .infinity,
            maxHeight: .infinity
        )
        .background(Color.red)
    }

}

3. Использование ZStack и полноразмерный фон View

В этом примере показано, как встроить ваш VStack внутри ZStackс верхним ведущим выравниванием. Обратите внимание, какColor view используется для установки максимальной ширины и высоты:

struct ContentView: View {

    var body: some View {
        ZStack(alignment: .topLeading) {
            Color.red
                .frame(maxWidth: .infinity, maxHeight: .infinity)

            VStack(alignment: .leading) {
                Text("Title")
                    .font(.title)
                Text("Content")
                    .font(.body)
            }
        }
    }

}

4. Использование GeometryReader

GeometryReaderимеет следующее объявление:

Представление контейнера, которое определяет свое содержимое как функцию своего собственного размера и координатного пространства. [...] Это представление возвращает гибкий предпочтительный размер родительскому макету.

В приведенном ниже фрагменте кода показано, как использовать GeometryReader выровнять ваш VStack с верхними ведущими ограничениями и полноразмерным фреймом:

struct ContentView : View {

    var body: some View {
        GeometryReader { geometryProxy in
            VStack(alignment: .leading) {
                Text("Title")
                    .font(.title)
                Text("Content")
                    .font(.body)
            }
            .frame(
                width: geometryProxy.size.width,
                height: geometryProxy.size.height,
                alignment: .topLeading
            )
        }
        .background(Color.red)
    }

}

5. Использование overlay(_:alignment:) метод

Если вы хотите выровнять VStack с верхними ведущими ограничениями поверх существующего полного размера View, вы можете использовать overlay(_:alignment:) метод:

struct ContentView: View {

    var body: some View {
        Color.red
            .frame(
                maxWidth: .infinity,
                maxHeight: .infinity
            )
            .overlay(
                VStack(alignment: .leading) {
                    Text("Title")
                        .font(.title)
                    Text("Content")
                        .font(.body)
                },
                alignment: .topLeading
            )
    }

}

Дисплей:

Альтернативное расположение стеков, которое работает и, возможно, немного более интуитивно понятно, заключается в следующем:

struct ContentView: View {
    var body: some View {
        HStack() {
            VStack(alignment: .leading) {
                Text("Hello World")
                    .font(.title)
                Text("Another")
                    .font(.body)
                Spacer()
            }
            Spacer()
        }.background(Color.red)
    }
}

Контент также можно легко переместить, удалив Spacer() в случае необходимости.

Существует лучший способ!

Чтобы сделать VStack заполнить ширину родительского вы можете использовать GeometryReader и установить рамку. (.relativeWidth(1.0) должно работать, но, видимо, не сейчас)

struct ContentView : View {
    var body: some View {
        GeometryReader { geometry in
            VStack {
                Text("test")
            }
                .frame(width: geometry.size.width,
                       height: nil,
                       alignment: .topLeading)
        }
    }
}

Чтобы сделать VStack ширина фактического экрана вы можете использовать UIScreen.main.bounds.width при установке рамки вместо использования GeometryReader, но я думаю, что вы, вероятно, хотели ширину родительского представления.

Кроме того, этот способ имеет дополнительное преимущество, не добавляя пробелы в вашем VStack что может произойти (если у вас есть интервал), если вы добавили HStack с Spacer() как это относится к VStack,

ОБНОВЛЕНИЕ - НЕ ЛУЧШЕ!

После проверки принятого ответа я понял, что принятый ответ на самом деле не работает! На первый взгляд кажется, что работает, но если вы обновите VStack иметь зеленый фон, вы заметите VStack по-прежнему такой же ширины.

struct ContentView : View {
    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {
                Text("Hello World")
                    .font(.title)
                Text("Another")
                    .font(.body)
                Spacer()
                }
                .background(Color.green)
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
                .background(Color.red)
        }
    }
}

Это потому что .frame(...) фактически добавляет другое представление к иерархии представления, и это представление в конечном итоге заполняет экран. Однако VStack до сих пор нет.

Эта проблема также, похоже, совпадает с моим ответом и может быть проверена с использованием того же подхода, что и выше (установка разных цветов фона до и после .frame(...), Единственный способ, который, кажется, фактически расширяет VStack это использовать проставки:

struct ContentView : View {
    var body: some View {
        VStack(alignment: .leading) {
            HStack{
                Text("Title")
                    .font(.title)
                Spacer()
            }
            Text("Content")
                .lineLimit(nil)
                .font(.body)
            Spacer()
        }
            .background(Color.green)
    }
}

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

extension View {
    func fillParent(alignment:Alignment = .center) -> some View {
        return GeometryReader { geometry in
            self
                .frame(width: geometry.size.width,
                       height: geometry.size.height,
                       alignment: alignment)
        }
    }
}

поэтому, используя запрошенный пример, вы получите

struct ContentView : View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("Title")
                .font(.title)

            Text("Content")
                .lineLimit(nil)
                .font(.body)
        }
        .fillParent(alignment:.topLeading)
        .background(Color.red)
    }
}

(обратите внимание, что распорка больше не нужна)

Самый простой способ решить эту проблему - использовать ZStack + .edgesIgnoringSafeArea(.all)

struct TestView : View {

    var body: some View {
        ZStack() {
            Color.yellow.edgesIgnoringSafeArea(.all)
            VStack {
                Text("Hello World")
            }
        }
    }
}

Способ номер 1 -> Использование MaxWidth и MaxHeight

      import SwiftUI

struct SomeView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.red)
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

Способ номер 2 -> Использование границ главного экрана

      import SwiftUI

struct SomeView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
        }
        .frame(maxWidth: UIScreen.main.bounds.width, maxHeight: UIScreen.main.bounds.height)
        .background(.red)
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

Способ номер 3 -> Использование программы чтения геометрии

      import SwiftUI

struct SomeView: View {
    var body: some View {
        GeometryReader { geometryReader in
            VStack {
                Text("Hello, World!")
            }
            .frame(maxWidth: geometryReader.size.width, maxHeight: geometryReader.size.height)
            .background(.red)
        }
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

Способ № 4 -> Использование прокладок

      import SwiftUI

struct SomeView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
            HStack{
                Spacer()
            }
            Spacer()
        }
        .background(.red)
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

Использовать это

.edgesIgnoringSafeArea(.all)

Хорошее решение и без "хитростей" - это забытое ZStack

ZStack(alignment: .top){
    Color.red
    VStack{
        Text("Hello World").font(.title)
        Text("Another").font(.body)
    }
}

Результат:

Вы можете сделать это с помощью GeometryReader

GeometryReader

Код:

struct ContentView : View {
    var body: some View {
        GeometryReader { geometry in
            VStack {
               Text("Turtle Rock").frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading).background(Color.red)
            }
        }
    }
}

Ваш вывод, как:

Еще одна альтернатива - разместить одно из подпредставлений внутри HStack и разместить Spacer() после этого:

struct ContentView : View {
    var body: some View {
        VStack(alignment: .leading) {

            HStack {
                Text("Title")
                    .font(.title)
                    .background(Color.yellow)
                Spacer()
            }

            Text("Content")
                .lineLimit(nil)
                .font(.body)
                .background(Color.blue)

            Spacer()
            }

            .background(Color.red)
    }
}

в результате чего:

https://i.s tack.imgur.com/T4QJb.png

Это полезный фрагмент кода:

extension View {
    func expandable () -> some View {
        ZStack {
            Color.clear
            self
        }
    }
}

Сравните результаты с и без .expandable() модификатор:

Text("hello")
    .background(Color.blue)

-

Text("hello")
    .expandable()
    .background(Color.blue)

Это то, что у меня сработало (ScrollView (необязательно), чтобы при необходимости можно было добавить больше контента, плюс центрированный контент):

import SwiftUI

struct SomeView: View {
    var body: some View {
        GeometryReader { geometry in
            ScrollView(Axis.Set.horizontal) {
                HStack(alignment: .center) {
                    ForEach(0..<8) { _ in
                        Text("")
                    }
                }.frame(width: geometry.size.width, height: 50)
            }
        }
    }
}

// MARK: - Preview

#if DEBUG
struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}
#endif

Результат

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

struct DividerTest: View {
var body: some View {
    VStack(alignment: .leading) {
        Text("Foo")
        Text("Bar")
        Divider()
    }.background(Color.red)
  }
}

Вот еще один способ сэкономить время в ваших проектах:

Намного меньше кода и многоразового использования по сравнению с другими ответами, которые нельзя использовать повторно!


      extension View {
    
    var maxedOut: some View {
        
        return Color.clear
            .overlay(self, alignment: .center)

    }
    
    func maxedOut(color: Color = Color.clear, alignment: Alignment = Alignment.center) -> some View {
        
        return color
            .overlay(self, alignment: alignment)
        
    }
    
}

вариант использования:

      struct ContentView: View {

    var body: some View {

        Text("Hello, World!")
            .maxedOut
            .background(Color.blue)
        
        Text("Hello, World!")
            .maxedOut(color: Color.red)
  
    }
}

⚠️ Важное замечание!

Все остальные решения просто добавляют рамку вокруг контента!

✅ но это решение меняет фактический кадр !


Простое и правильное расширение

Вы можете использовать этот модификатор

      .flexible(width: true, height: false)

Демо

Обратите внимание, как содержимое выравнивается точно так, как вы назначаете в исходном стеке.


Код, стоящий за этим ( FlexibleViewModifier.swift )

      extension View {
    func flexible(width: Bool, height: Bool) -> some View {
        self.modifier(MatchingParentModifier(width: width, height: height))
    }
}

struct MatchingParentModifier: ViewModifier {
    @State private var intrinsicSize: CGSize = UIScreen.main.bounds.size
    private let intrinsicWidth:  Bool
    private let intrinsicHeight:  Bool

    init(width: Bool, height: Bool) {
        intrinsicWidth = !width
        intrinsicHeight = !height
    }

    func body(content: Content) -> some View {
        GeometryReader { _ in
            content.modifier(intrinsicSizeModifier(intrinsicSize: $intrinsicSize))
        }
        .frame(
            maxWidth: intrinsicWidth ? intrinsicSize.width : nil,
            maxHeight: intrinsicHeight ? intrinsicSize.height : nil
        )
    }
}

struct intrinsicSizeModifier: ViewModifier {
    @Binding var intrinsicSize: CGSize

    func body(content: Content) -> some View {
        content.readIntrinsicContentSize(to: $intrinsicSize)
    }
}

struct IntrinsicContentSizePreferenceKey: PreferenceKey {
    static let defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

extension View {
    func readIntrinsicContentSize(to size: Binding<CGSize>) -> some View {
        background(
            GeometryReader {
                Color.clear.preference(
                    key: IntrinsicContentSizePreferenceKey.self,
                    value: $0.size
                )
            }
        )
        .onPreferenceChange(IntrinsicContentSizePreferenceKey.self) {
            size.wrappedValue = $0
        }
    }
}

Дизайн страницы входа с использованием SwiftUI

import SwiftUI

struct ContentView: View {

    @State var email: String = "william@gmail.com"
    @State var password: String = ""
    @State static var labelTitle: String = ""


    var body: some View {
        VStack(alignment: .center){
            //Label
            Text("Login").font(.largeTitle).foregroundColor(.yellow).bold()
            //TextField
            TextField("Email", text: $email)
                .textContentType(.emailAddress)
                .foregroundColor(.blue)
                .frame(minHeight: 40)
                .background(RoundedRectangle(cornerRadius: 10).foregroundColor(Color.green))

            TextField("Password", text: $password) //Placeholder
                .textContentType(.newPassword)
                .frame(minHeight: 40)
                .foregroundColor(.blue) // Text color
                .background(RoundedRectangle(cornerRadius: 10).foregroundColor(Color.green))

            //Button
            Button(action: {

            }) {
                HStack {
                    Image(uiImage: UIImage(named: "Login")!)
                        .renderingMode(.original)
                        .font(.title)
                        .foregroundColor(.blue)
                    Text("Login")
                        .font(.title)
                        .foregroundColor(.white)
                }


                .font(.headline)
                .frame(minWidth: 0, maxWidth: .infinity)
                .background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing))
                .cornerRadius(40)
                .padding(.horizontal, 20)

                .frame(width: 200, height: 50, alignment: .center)
            }
            Spacer()

        }.padding(10)

            .frame(minWidth: 0, idealWidth: .infinity, maxWidth: .infinity, minHeight: 0, idealHeight: .infinity, maxHeight: .infinity, alignment: .top)
            .background(Color.gray)

    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Просто добавьColor.clearна дноVStack, просто как тот :)

      struct ContentView: View {
   var body: some View {
     VStack(alignment: .leading) {
       Text("Title")

       Color.clear
     }
     .background(Color.red)
   }
 }
       var body: some View {
      VStack {
           CarouselView().edgesIgnoringSafeArea(.all)           
           List {
                ForEach(viewModel.parents) { k in
                    VideosRowView(parent: k)
                }
           }
    }
 }
Другие вопросы по тегам