Tạo VStack lấp đầy chiều rộng của màn hình trong SwiftUI


111

Đưa ra mã này:

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

Nó dẫn đến kết quả là:

xem trước

Làm cách nào để tôi có thể VStacklấp đầy chiều rộng của màn hình ngay cả khi các nhãn / thành phần văn bản không cần chiều rộng đầy đủ?

Một mẹo mà tôi đã tìm thấy là chèn một khoảng trống HStack trong cấu trúc như sau:

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

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

    Spacer()
}

Điều này mang lại thiết kế mong muốn:

kết quả mong muốn

Có cách nào tốt hơn?


1
Xem người trả lời của tôi hiển thị tối đa 5 cách thay thế để có kết quả tương tự.
Imanou Petit

Câu trả lời:


183

Hãy thử sử dụng công cụ sửa đổi .frame với các tùy chọn sau:

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

Đây được mô tả là một khung linh hoạt ( xem tài liệu ), sẽ kéo dài để lấp đầy toàn bộ màn hình và khi có thêm không gian, nó sẽ căn giữa nội dung bên trong.


Các subviews không sắp xếp sang trái ( .leading). Đưa toàn bộ chiều rộng nếu đặt trước.background(Color.red)
ielyamani

Đã thêm mã để căn chỉnh bên trái. Sẽ khớp chính xác ngay bây giờ
svarrall

9
Cái này sai. Nền .backgound(Color.red)là nền của chế độ xem khung, nhưng không phải nền của VStackchế độ xem trong khung. (Lý do cho điều này được giải thích trong các video WWDC: P). Nếu bạn đặt .background(Color.green)trước, .framebạn sẽ thấy VStackvẫn có chiều rộng phù hợp với nội dung của nó trong khi khung có nền màu đỏ ... Khi bạn đặt .frame(...), nó không phải là chỉnh sửa VStackmà thực sự tạo ra một chế độ xem khác.
Jake

2
@Jake có vẻ như một công thức cho một thảm họa
Sudara

Có một câu hỏi tho: Tại sao sử dụng alignment: .topLeading vì chúng ta đang có VStack (alignment: .leading) ?? Cảm ơn vì đã chia sẻ
UIChris

35

Một cách sắp xếp xếp chồng thay thế hoạt động và có lẽ trực quan hơn một chút là như sau:

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

Nội dung cũng có thể dễ dàng được định vị lại bằng cách xóa các Spacer()'nếu cần.

thêm / xóa Dấu cách để thay đổi vị trí


4
Tốt thay thế 👍🏻Liked gif
ielyamani

1
Tôi thấy điều này vừa trực quan hơn vừa phù hợp hơn với câu hỏi của OP, câu hỏi mà tôi đặt ra là làm thế nào để phần tử Stack lấp đầy vùng chứa của nó hơn là cách chèn một vùng chứa xung quanh phần tử.
brotskydotcom

1
Tôi thấy đây là câu trả lời tốt nhất phù hợp với ý tưởng đằng sau SwiftUI
krummens

1
Chắc chắn là câu trả lời SwiftUI-y nhất ở đây cho một bố cục chung như vậy.
dead_can_dance

30

Có một cách tốt hơn!

Để làm cho phần VStacklấp đầy bằng chiều rộng của nó, bạn có thể sử dụng a GeometryReadervà thiết lập khung. ( .relativeWidth(1.0)sẽ hoạt động nhưng dường như không phải lúc này)

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

Để tạo VStackchiều rộng của màn hình thực, bạn có thể sử dụng UIScreen.main.bounds.widthkhi đặt khung thay vì sử dụng a GeometryReader, nhưng tôi tưởng tượng bạn có thể muốn chiều rộng của chế độ xem chính.

Ngoài ra, cách này có lợi ích bổ sung là không thêm khoảng cách trong của bạn VStack, điều có thể xảy ra (nếu bạn có khoảng cách) nếu bạn thêm một HStackvới a Spacer()vì nội dung của nó VStack.

CẬP NHẬT - KHÔNG CÓ CÁCH NÀO TỐT HƠN!

Sau khi kiểm tra câu trả lời được chấp nhận, tôi nhận ra rằng câu trả lời được chấp nhận không thực sự hoạt động! Thoạt nhìn, nó có vẻ hoạt động, nhưng nếu bạn cập nhật VStackđể có nền màu xanh lá cây, bạn sẽ nhận thấy VStacknó vẫn có cùng chiều rộng.

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

Điều này là do .frame(...)thực sự đang thêm một chế độ xem khác vào hệ thống phân cấp chế độ xem và chế độ xem đó sẽ lấp đầy màn hình. Tuy nhiên, VStackvẫn không.

Vấn đề này cũng có vẻ giống trong câu trả lời của tôi và có thể được kiểm tra bằng cách sử dụng cách tiếp cận tương tự như trên (đặt các màu nền khác nhau trước và sau .frame(...). Cách duy nhất dường như thực sự mở rộng VStacklà sử dụng các dấu cách:

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

Đã thử relativeWidth, không hoạt động. Bằng cách nào đó GeometryReaderhoạt động nếu .frameđược đặt trước.background(Color.red)
ielyamani

Tôi không thể thấy GeometryReadertrong danh sách được hiển thị khi tôi "Thêm Công cụ sửa đổi" sau tôi Show SwiftUI Inspectorhoặc bất kỳ vị trí nào khác trong giao diện người dùng. Làm thế nào bạn khám phá ra GeometryReader?
đi ngày

1
@gone - Stack Overflow :)
Jake ngày

17

Với Swift 5.2 và iOS 13.4, theo nhu cầu của bạn, bạn có thể sử dụng một trong các ví dụ sau để điều chỉnh các VStackràng buộc hàng đầu và khung kích thước đầy đủ.

Lưu ý rằng tất cả các đoạn mã bên dưới đều dẫn đến cùng một màn hình, nhưng không đảm bảo khung hiệu quả của VStackcũng như số lượng Viewphần tử có thể xuất hiện trong khi gỡ lỗi phân cấp chế độ xem.


1. frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:)Phương pháp sử dụng

Cách tiếp cận đơn giản nhất là đặt khung của bạn VStackvới chiều rộng và chiều cao tối đa, đồng thời vượt qua sự căn chỉnh cần thiết trong 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)
    }

}

Thay vào đó, nếu việc đặt khung tối đa với căn chỉnh cụ thể cho các Views của bạn là một mẫu phổ biến trong cơ sở mã của bạn, bạn có thể tạo một phương thức mở rộng Viewcho nó:

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. Sử dụng Spacers để bắt buộc căn chỉnh

Bạn có thể nhúng VStackvào bên trong một kích thước đầy đủ HStackvà sử dụng dấu cuối và dấu dưới Spacerđể bắt buộc VStackcăn chỉnh hàng đầu:

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. Sử dụng ZStacknền kích thước đầy đủView

Ví dụ này cho thấy cách nhúng VStackvào bên trong của bạn một ZStackliên kết hàng đầu. Lưu ý cách Colorchế độ xem được sử dụng để đặt chiều rộng và chiều cao tối đa:

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. Sử dụng GeometryReader

GeometryReaderkhai báo sau :

Chế độ xem vùng chứa xác định nội dung của nó dưới dạng một hàm có kích thước và không gian tọa độ riêng. [...] Dạng xem này trả về kích thước ưu tiên linh hoạt cho bố cục mẹ của nó.

Đoạn mã dưới đây cho biết cách sử dụng GeometryReaderđể căn chỉnh của bạn VStackvới các ràng buộc hàng đầu và khung kích thước đầy đủ:

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:)Phương pháp sử dụng

Nếu bạn muốn căn chỉnh của mình VStackvới các ràng buộc hàng đầu trên kích thước đầy đủ hiện có View, bạn có thể sử dụng overlay(_:alignment:)phương pháp:

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

}

Trưng bày:


9

Cách đơn giản nhất mà tôi quản lý để giải quyết vấn đề là sử dụng ZStack + .edgesIgnoringSafeArea (.all)

struct TestView : View {

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

4

Bạn có thể làm điều đó bằng cách sử dụng GeometryReader

GeometryReader

Mã:

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

Đầu ra của bạn như:

nhập mô tả hình ảnh ở đây



4

Một giải pháp tốt và không có "contraptions" bị lãng quên ZStack

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

Kết quả:

nhập mô tả hình ảnh ở đây


3

Một cách thay thế khác là đặt một trong các chế độ xem phụ bên trong một HStackvà đặt Spacer()sau nó:

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

dẫn đến:

HStack bên trong VStack


1
Imho, đây là giải pháp thanh lịch nhất
Vyacheslav

3

Đây là những gì phù hợp với tôi ( ScrollView(tùy chọn) để nhiều nội dung hơn có thể được thêm vào nếu cần, cộng với nội dung được căn giữa):

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

Kết quả

tập trung


2

Tôi biết điều này sẽ không hiệu quả với tất cả mọi người, nhưng tôi nghĩ rằng thật thú vị khi chỉ thêm một dải phân cách sẽ giải quyết được vấn đề này.

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

2

Đây là một đoạn mã hữu ích:

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

So sánh kết quả có và không có công cụ .expandable()sửa đổi:

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

-

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

nhập mô tả hình ảnh ở đây


2

Bạn có thể sử dụng GeometryReader trong một tiện ích mở rộng tiện dụng để điền vào phụ huynh

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

vì vậy bằng cách sử dụng ví dụ được yêu cầu, bạn nhận được

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

(lưu ý rằng miếng đệm không còn cần thiết nữa)

nhập mô tả hình ảnh ở đây


2

nhập mô tả hình ảnh ở đây

Thiết kế trang đăng nhập bằng cách sử dụng 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()
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.