Заставить 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)
}
}
В качестве альтернативы, если установить максимальный кадр с определенным выравниванием для вашего View
s - это общий шаблон в вашей кодовой базе, вы можете создать метод расширения на 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. Использование Spacer
s для принудительного выравнивания
Вы можете встроить свой VStack
внутри в полный размер HStack
и используйте трейлинг и дно Spacer
s, чтобы заставить ваш 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()
}
}
Хорошее решение и без "хитростей" - это забытое ZStack
ZStack(alignment: .top){
Color.red
VStack{
Text("Hello World").font(.title)
Text("Another").font(.body)
}
}
Результат:
Вы можете сделать это с помощью 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)
}
}
в результате чего:
Это полезный фрагмент кода:
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)
}
}
}
}