Spraw, aby VStack wypełnił szerokość ekranu w SwiftUI


119

Biorąc pod uwagę ten kod:

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

Wynika z tego interfejs:

zapowiedź

Jak mogę VStackwypełnić całą szerokość ekranu, nawet jeśli etykiety / komponenty tekstowe nie wymagają pełnej szerokości?

Sztuczka, którą znalazłem, polega na wstawieniu pustego HStack elementu do struktury w następujący sposób:

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

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

    Spacer()
}

Który daje pożądany projekt:

Pożądane wyjście

Czy jest lepszy sposób?


1
Zobacz moją odpowiedź, która pokazuje do 5 alternatywnych sposobów uzyskania podobnego wyniku.
Imanou Petit

Odpowiedzi:


199

Spróbuj użyć modyfikatora .frame z następującymi opcjami:

.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, 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)
    }
}

Jest to opisywane jako elastyczna ramka ( patrz dokumentacja ), która rozciąga się, aby wypełnić cały ekran, a gdy ma dodatkową przestrzeń, wyśrodkuje zawartość wewnątrz niej.


W subviews nie wyrównać do lewej ( .leading). .background(Color.red)
Przyjmuje

Dodano kod do wyrównywania również do lewej. Powinien pasować dokładnie teraz
svarrall

12
To jest źle. Jest .backgound(Color.red)to tło widoku ramki, ale nie tło tego, VStackco jest w widoku ramki. (Przyczyna tego jest wyjaśniona w filmach WWDC: P). Jeśli umieścisz go .background(Color.green)przed .frame, zobaczysz VStack, że klatka ma szerokość, która pasuje do jej zawartości, podczas gdy ramka ma czerwone tło ... Kiedy wstawisz .frame(...), nie edytujesz VStack, w rzeczywistości tworzy inny widok.
Jake

2
@Jake wydaje się być przepisem na katastrofę
Sudara

1
Mam pytanie: po co używać wyrównania: .topLeading, skoro mamy VStack (alignment: .leading) ?? Dzięki za udostępnienie
UIChris

36

Alternatywny układ układania, który działa i być może nieco bardziej intuicyjny, to:

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

Zawartość można również łatwo zmienić położenie, usuwając je Spacer(), jeśli to konieczne.

dodawanie / usuwanie elementów dystansowych w celu zmiany pozycji


5
Dobra alternatywa 👍🏻Liked gif
ielyamani,

1
Uważam, że jest to zarówno bardziej intuicyjne, jak i bardziej zgodne z pytaniem OP, które dotyczyło tego, jak sprawić, by element Stack wypełnił swój pojemnik, a nie jak wstawić kontener wokół elementu.
brotskydotcom

1
Uważam, że to najlepsza odpowiedź, która jest zgodna z ideą SwiftUI
krummens

1
Zdecydowanie najbardziej SwiftUI-y odpowiedź na tak powszechny układ.
dead_can_dance

30

Jest lepszy sposób!

Aby VStackwypełnić szerokość jego rodzica, możesz użyć GeometryReaderi ustawić ramkę. ( .relativeWidth(1.0)powinno działać, ale najwyraźniej teraz nie)

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

Aby uzyskać VStackszerokość rzeczywistego ekranu, możesz użyć UIScreen.main.bounds.widthpodczas ustawiania ramki zamiast używać GeometryReader, ale wyobrażam sobie, że prawdopodobnie chciałeś szerokości widoku rodzica.

Ponadto ten sposób ma dodatkową zaletę polegającą na tym, że nie dodaje się odstępów w twoim, VStackco może się zdarzyć (jeśli masz odstępy), jeśli dodałeś HStackz a Spacer()jako zawartość do VStack.

AKTUALIZACJA - NIE MA LEPSZEGO SPOSOBU!

Po sprawdzeniu zaakceptowanej odpowiedzi zdałem sobie sprawę, że zaakceptowana odpowiedź tak naprawdę nie działa! Na pierwszy rzut oka wygląda na to, że działa, ale jeśli zaktualizujesz go, VStackaby miał zielone tło, zauważysz, że VStackma nadal tę samą szerokość.

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

Dzieje się tak, ponieważ w .frame(...)rzeczywistości dodaje kolejny widok do hierarchii widoków i ten widok kończy się wypełnieniem ekranu. Jednak VStacknadal nie.

Ta kwestia również wydaje się być taka sama w mojej odpowiedzi i można ją sprawdzić, używając tego samego podejścia, co powyżej (umieszczając różne kolory tła przed i po .frame(...). Jedynym sposobem, który wydaje się faktycznie poszerzać, VStackjest użycie odstępników:

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

Próbował relativeWidth, nie działał. Jakoś GeometryReaderdziała, jeśli .framezostanie umieszczony wcześniej.background(Color.red)
ielyamani

Nie widzę GeometryReaderna liście wyświetlanej, gdy „Dodaj modyfikator” po I Show SwiftUI Inspectorlub w jakimkolwiek innym miejscu w interfejsie użytkownika. Jak odkryłeś GeometryReader?
odszedł

1
@gone - Stack Overflow :)
Jake

20

Dzięki Swift 5.2 i iOS 13.4, w zależności od potrzeb, możesz użyć jednego z poniższych przykładów, aby dopasować swoje VStackdo najważniejszych wiodących ograniczeń i pełnowymiarowej ramki.

Zwróć uwagę, że poniższe fragmenty kodu powodują ten sam ekran, ale nie gwarantują efektywnej klatki VStackani liczby Viewelementów, które mogą pojawić się podczas debugowania hierarchii widoków.


1. Za pomocą frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:)metody

Najprostszym podejściem jest ustawienie ramy VStackz maksymalną szerokością i wysokością, a także przejście wymaganego wyrównania w 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)
    }

}

Alternatywnie, jeśli ustawienie maksymalnej klatki z określonym wyrównaniem dla twojego Views jest częstym wzorcem w twoim kodzie, możesz utworzyć Viewdla niego metodę rozszerzenia :

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. Użycie Spacers do wymuszenia wyrównania

Możesz osadzić swoje VStackwnętrze w pełnym rozmiarze HStacki użyć końcowych i dolnych, Spaceraby wymusić VStackwyrównanie górnego wiodącego:

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. Używając ZStackai pełnowymiarowego tłaView

Ten przykład pokazuje, jak osadzić VStackwewnątrz a, ZStackktóre ma górne wyrównanie wiodące. Zwróć uwagę, jak Colorwidok jest używany do ustawiania maksymalnej szerokości i wysokości:

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. Korzystanie GeometryReader

GeometryReaderposiada następującą deklarację :

Widok kontenera, który definiuje zawartość jako funkcję własnego rozmiaru i przestrzeni współrzędnych. [...] Ten widok zwraca elastyczny preferowany rozmiar do układu nadrzędnego.

Poniższy fragment kodu pokazuje, jak używać go GeometryReaderdo wyrównywania VStackz najważniejszymi wiodącymi ograniczeniami i pełnowymiarową ramką:

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. Za pomocą overlay(_:alignment:)metody

Jeśli chcesz wyrównać swoje VStackz najważniejszymi wiodącymi ograniczeniami na szczycie istniejącego pełnego rozmiaru View, możesz użyć overlay(_:alignment:)metody:

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

}

Pokaz:


10

Najprostszym sposobem, w jaki udało mi się rozwiązać ten problem, było użycie ZStack + .edgesIgnoringSafeArea (.all)

struct TestView : View {

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


4

Możesz to zrobić za pomocą GeometryReader

GeometryReader

Kod:

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

Twoje wyjście, takie jak:

wprowadź opis obrazu tutaj


4

Dobre rozwiązanie i bez „urządzeń” jest zapomniane ZStack

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

Wynik:

wprowadź opis obrazu tutaj


3

Jeszcze jedną alternatywą jest umieszczenie jednego z widoków podrzędnych wewnątrz an HStacki umieszczenie Spacer()po nim:

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

w wyniku:

HStack wewnątrz VStack


1
Imho, to najbardziej eleganckie rozwiązanie
Wiaczesław

3

To zadziałało w moim przypadku ( ScrollView(opcjonalnie), więc w razie potrzeby można dodać więcej treści, a także zawartość wyśrodkowaną):

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

Wynik

wyśrodkowany


2

Wiem, że to nie zadziała dla wszystkich, ale pomyślałem, że interesujące jest to, że samo dodanie dzielnika rozwiązuje ten problem.

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

2

Oto przydatny fragment kodu:

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

Porównaj wyniki .expandable()zi bez modyfikatora:

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

-

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

wprowadź opis obrazu tutaj


2

wprowadź opis obrazu tutaj

Projekt strony logowania przy użyciu 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()
    }
}

1

Możesz użyć GeometryReader w poręcznym rozszerzeniu, aby wypełnić element nadrzędny

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

więc korzystając z żądanego przykładu, otrzymasz

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

(uwaga, element dystansowy nie jest już potrzebny)

wprowadź opis obrazu tutaj


to nie jest pełny ekran.
Duck

pytanie nie wymagało pełnego ekranu, ale wymagało pełnej szerokości. Jeśli chcesz wyjść poza bezpieczny obszar, użyj. edgeIgnoringSafeArea
Confused Vorlon

0
 var body: some View {
      VStack {
           CarouselView().edgesIgnoringSafeArea(.all)           
           List {
                ForEach(viewModel.parents) { k in
                    VideosRowView(parent: k)
                }
           }
    }
 }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.