Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on October 16, 2021

Section in Grid – SwiftUI Notes #54

SwiftUI

Contents

  • Chuẩn bị
  • Section
    • Vấn đề
    • Add Section
  • Header
    • Title
    • Custom
  • LazyHGrid
  • PinnedScrollableViews
  • Tạm kết

Chào mừng bạn đến với Fx Studio. Chúng ta tiếp tục hành trình bất tận trong vũ trụ SwiftUI. Chủ đề bài viết này là Section in Grid, cách bạn sẽ trình bày bố cục giao diện theo Grid Layout và có phân chia nội dung. Về bản chất, Section trong Grid Layout cũng tương tự như List hay UICollectionView. Nắm được cấu tạo & cách hoạt động của Section trong Grid sẽ giúp bạn chủ động hơn trong việc bố cục giao diện phực tạp.

Nếu bạn chưa biết gì về Grid Layout trong SwiftUI, thì có thể đọc lại các bài viết trước.

    • Basic Grid Layout
    • GridItem & Configuring GridLayout

Còn nếu mọi việc đã ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

Về mặt tool và version, các bạn tham khảo như sau:

    • SwiftUI 3.0
    • Xcode 13

Về mặt kiến thức, bạn cần biết trước các kiến thức cơ bản với SwiftUI & SwiftUI App. Tham khảo các bài viết sau, nếu bạn chưa đọc qua SwiftUI:

    • Làm quen với SwiftUI
    • Cơ bản về ứng dụng SwiftUI App

(Mặc định, mình xem như bạn đã biết về cách tạo project với SwiftUI & SwiftUI App rồi.)

Về mặt demo, bạn chỉ cần thực hiện demo trên các SwiftUI View đơn giản. Mình sẽ thực hiện các View riêng biệt với nhau, nên bạn không cần lo lắng gì nhiều về tính liên kết của các View trong một Project. Về mặt giao diện thì khá là đơn giản à.

(Hoặc bạn có thể checkout project demo tại đây.)

Section

Đây cũng là vấn đề muôn thuở. Bạn có nhiều danh sách cần được hiển thị hoặc chia thành nhiều bố cục khác nhau. Với List & TableView thì chúng ta sẽ chia chúng thành nhiều Section khác nhau. Và vấn đề này cũng lại xuất hiên trên Grid, nhưng …

Tại sao phải cần Section trong Grid làm gì?

Vấn đề

Để trả lời câu hỏi đó, thì chúng ta sẽ xem qua ví dụ code sau:

struct SectionInGridDemoView: View {
    
    private var configGridItem = [
        GridItem(.fixed(100), spacing: 10),
        GridItem(.fixed(100), spacing: 10),
        GridItem(.fixed(100), spacing: 10)
    ]
    
    var body: some View {
        ScrollView(.vertical) {
            LazyVGrid(
                columns: configGridItem,
                spacing: 10
            ) {
                ForEach(1...10, id: \.self) { index in
                    Text("1.\(index)")
                        .frame(maxWidth: .infinity, minHeight: 50)
                        .background(Color.random)
                        .clipShape(
                            RoundedRectangle(cornerRadius: 10.0)
                        )
                }
            }
            .padding(.horizontal)
        }
        .frame(maxHeight: .infinity)
    }
}

Trong đó:

  • Một LazyVGrid bình thường
  • Config với 3 cột GridItem dạng .fixed
  • ForEach lặp từ 1 đến 10

Kết quả sẽ trông như thế này.

Section

Mọi thứ vẫn đẹp. Chúng ta sẽ thêm tiếp 1 đoạn ForEach tiếp tục nữa vào LazyVGrid để xem Grid biến đổi như thế nào.

ForEach(11...20, id: \.self) { index in
    Text("2.\(index)")
        .frame(maxWidth: .infinity, minHeight: 50)
        .background(Color.random)
        .clipShape(
            RoundedRectangle(cornerRadius: 10.0)
        )
}

À há, vẫn không báo lỗi và không bị crash. Tuy nhiên … kết quả sẽ có một chút bất thường.

Vấn đề chính là đây. Bạn sẽ thấy các phần tử của ForEach thứ 2 tiếp tục nối vào ForEach thứ 1. Chúng liên kết với nhau tạo thành một thể thống nhất. Và không có sự tách biệt ở đây.

Ta sẽ thử dùng với Section xem như thế nào.

Add Section

Bạn sẽ cập nhật lại nội dung của LazyVGrid thêm các đối tượng Section cho các ForEach ở trên nhóe. Tham khảo code như sau:

Section {
    ForEach(1...9, id: \.self) { index in
        Text("1.\(index)")
            .frame(maxWidth: .infinity, minHeight: 50)
            .background(Color.random)
            .clipShape(
                RoundedRectangle(cornerRadius: 10.0)
            )
    }
}

Section {
    ForEach(10...19, id: \.self) { index in
        Text("2.\(index)")
            .frame(maxWidth: .infinity, minHeight: 50)
            .background(Color.random)
            .clipShape(
                RoundedRectangle(cornerRadius: 10.0)
            )
    }
}

Trong đó:

  • Các đối tượng Section bọc lại các ForEach
  • Các Section chứa trong LazyVGrid

Với các Section, giúp cho Grid của bạn tách biệt các phần nội dung hay các danh sách riêng lẻ với nhau. Bạn bấm Resume và xem kết quả nhóe!

Section

Lúc này, mọi thứ nhìn sạch đẹp rồi đó. Hi vọng qua vài ví dụ nhỏ cũng giúp bạn hiểu được ý nghĩa vì sao cần Section trong Grid. Cũng tương tự như là với List. Mỗi đối tượng sinh ra đều có ý nghĩa riêng hết. Ahihi!

Header

Chúng ta tiếp tục sang công việc tiếp theo cần xử lý Section. Bạn cần phải hiển thị thêm nội dung cho Section. Như bạn cũng đã biết thì ta có 2 view phụ cho nó:

  • Header
  • Footer

Chúng ta sẽ demo với Header thôi nhóe, còn lại bạn tự xử.

Title

Bạn cần thêm các đối số cho header của hàm khởi tạo của Section. Code thì khá đơn giản, vì chúng ta đã quá quen thuộc với List rồi. Bạn tham khảo thêm nhóe!

Section(header: Text("Section 1").font(.title)) {
    ForEach(1...10, id: \.self) { index in
        Text("1.\(index)")
            .frame(maxWidth: .infinity, minHeight: 50)
            .background(Color.random)
            .clipShape(
                RoundedRectangle(cornerRadius: 10.0)
            )
    }
}

Section(header: Text("Section 2").font(.title)) {
    ForEach(11...20, id: \.self) { index in
        Text("2.\(index)")
            .frame(maxWidth: .infinity, minHeight: 50)
            .background(Color.random)
            .clipShape(
                RoundedRectangle(cornerRadius: 10.0)
            )
    }
}

Sử dụng Text làm header & tùy chỉnh thêm một ít với modifier .font. Bấm Resume và xem kết quả nhóe!

Section

Chỉ vài thao tác nhỏ thì bạn đã có được Title cho Section rồi nhóe.

Custom

Tất nhiên, chúng ta luôn sẽ phải custom view. Với header thì cũng không ngoại lệ được. Điều này giúp bạn tiết kiệm code và tránh viết code quá nhiều trong khởi tạo một đối tượng.

Bạn sẽ thêm một function như sau:

private func customVHeader(with header: String) -> some View {
  Text(header)
    .font(.title2)
    .bold()
    .foregroundColor(.white)
    .padding()
    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
    .background(RoundedRectangle(cornerRadius: 10)
      .fill(Color.headerBackground))
}

Một function đơn giản và trả về với some View. Hiển thị nội dung title được truyền cho tham số của function. Bạn thay thế vào tham số header của Section.

var body: some View {
    ScrollView(.vertical) {
        LazyVGrid(
            columns: configGridItem,
            spacing: 10
        ) {
            Section(header: customVHeader(with: "Section 1")) {
                ForEach(1...10, id: \.self) { index in
                    Text("1.\(index)")
                        .frame(maxWidth: .infinity, minHeight: 50)
                        .background(Color.random)
                        .clipShape(
                            RoundedRectangle(cornerRadius: 10.0)
                        )
                }
            }
            
            Section(header: customVHeader(with: "Section 2")) {
                ForEach(11...20, id: \.self) { index in
                    Text("2.\(index)")
                        .frame(maxWidth: .infinity, minHeight: 50)
                        .background(Color.random)
                        .clipShape(
                            RoundedRectangle(cornerRadius: 10.0)
                        )
                }
            }
        }
        .padding(.horizontal)
    }
    .frame(maxHeight: .infinity)
}

Cũng khá đơn giản, ta cần truyền các String khác nhau để làm title cho mỗi Section. Cuối cùng, bấm Resume để xem tiếp kết quả nha!

Cũng ra gì và này nọ chứ. Bạn có thể tùy biến chúng đa dạng hơn nhiều so với ví dụ đơn giản của mình. Nhưng bạn sẽ chủ động hơn về mặt thiết kế giao diện cho ứng dụng của mình.

LazyHGrid

Chúng ta sẽ tiếp tục demo các Section trên cho đối tượng Grid thứ 2 là LazyHGrid. Mục đích để xem hiển thị như thế nào với các Section được scroll ngang. À, không phải đơn giản chỉ là thay cái tên từ LazyVGrid sang thành LazyHGrid nha.

Công việc cũng khá phức tạp.

Đầu tiên, ta cần tùy chỉnh lại lại array configGridItem trước đã. Cho chúng có khả năng linh hoạt một tí với .adaptive.

private var configGridItem = [
    GridItem(.flexible(minimum: 100), spacing: 10),
    GridItem(.flexible(minimum: 100), spacing: 10),
    GridItem(.flexible(minimum: 100), spacing: 10)
]

Tiếp theo, ta sẽ phải thay đổi lại lại function Custom Header cho Section. Tham khảo đoạn code sau nhóe!

private func customHHeader(with header: String) -> some View {
    Text(header)
      .bold()
      .frame(minWidth: 70)
      .rotationEffect(Angle(degrees: -90))
      .frame(maxWidth: .infinity, maxHeight: .infinity)
      .background(RoundedRectangle(cornerRadius: 10)
        .fill(Color.headerBackground))
}

Bạn cần chú ý tới:

  • Góc quay .rotationEffect
  • frame với maxWidth & maxHeight (đã hoán vị so với LazyVGrid)

Cuối cùng, là sử dụng LazyHGrid vào trong ScrollView nhóe. Tiếp tục tham khảo đoạn code sau:

var body: some View {
    ScrollView(.horizontal, showsIndicators: false) {
        LazyHGrid(
            rows: configGridItem,
            spacing: 10
        ) {
            Section(header: customHHeader(with: "Section 1")) {
                ForEach(1...10, id: \.self) { index in
                    Text("1.\(index)")
                        .frame(minWidth: 50, maxHeight: .infinity)
                        .background(Color.random)
                        .clipShape(
                            RoundedRectangle(cornerRadius: 10.0)
                        )
                }
            }
            
            Section(header: customHHeader(with: "Section 2")) {
                ForEach(11...20, id: \.self) { index in
                    Text("2.\(index)")
                        .frame(minWidth: 50, maxHeight: .infinity)
                        .background(Color.random)
                        .clipShape(
                            RoundedRectangle(cornerRadius: 10.0)
                        )
                }
            }
        }
        .padding(.horizontal)
    }
    .frame(maxHeight: .infinity)
}

Trong đó:

  • ScrollView sẽ được kích hoạt scroll ngang với .horizontal
  • LazyHGrid sẽ cần cung cấp giá trị cho tham số rows
  • Custom Header của Section sẽ gọi tới function customHHeader
  • Mỗi item trong Grid cần điều chỉnh lại frame cho phù hợp

Mọi thứ còn lại thì không thay đổi gì nhiều. Bạn bấm Live Preview và xem kết quả nhóe.

Section

Để thiết bị nằm ngang cho dễ quan sát. Bạn thử scroll qua lại xem như thế nào. Ahihi!

PinnedScrollableViews

PinnedScrollableViews là các View mà bạn có thể pin (hay luôn hiển thị) trên Grid Layout. Đây là một tính năng mới được thêm cho các LazyVGrid & LazyHGrid.

Đem lại một trải nghiệm tương tự như với UIKit. Và bạn sẽ không cần triệu hồi các UIKit để thực hiện nữa. Tất cả, đều có sẵn và chỉ cần sử dụng mà thôi.

Còn với Grid Layout cho cả 2 đối tượng LazyVGrid & LazyHGrid, thì đều có tham số pinnedView cho hàm khởi tạo của chúng. Ví dụ như sau:

LazyVGrid(
            columns: configGridItem,
            spacing: 10
            pinnedViews: [.sectionHeaders, .sectionFooters]
        ) {
        
        // ....
        
        }

Trong đó:

  • .sectionHeaders chính là các Header của Section
  • .sectionFooters chính là các Footer của Section

Khi các View được khởi tạo và bạn scroll chúng. Phần header hoặc footer sẽ ở nguyên vị trí của chúng, chỉ khi nào toàn bộ Section trôi qua thì chúng sẽ bị trôi theo.

Bạn hãy áp dụng vào 2 demo ở trên để xem kết quả nhóe, nhớ là thêm vài Section nữa (thay vì 2 cái như ví dụ).

  • LazyVGrid

  • LazyHGrid

Trông cũng khá ổn rồi đó, chúc bạn thành công nhóe. Ahihi!

Tạm kết

  • Thêm các Section cho Grid Layout
  • Custom các Header & Footer
  • Tùy biến Section riêng với LazyHGrid & LazyVGrid
  • Sử dụng các PinnedScrollableViews để dính các header hoặc footer trên Grid Layout

 

Okay! Tới đây, mình xin kết thúc bài viết về Section in Grid trong SwiftUI. Và nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.

  • Bạn có thể checkout code tại đây.
  • Bài viết tiếp theo tại đây.

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares24

Related Posts:

  • Tích hợp UIViewController (UIKit) vào SwiftUI Project - SwiftUI Notes #14
    Tích hợp UIViewController (UIKit) vào SwiftUI Project -…
  • Sử dụng Custom UIView vào SwiftUI Project - SwiftUI Notes #16
    Sử dụng Custom UIView vào SwiftUI Project - SwiftUI Notes…
  • Updating UI - SwiftUI Notes #3
    Updating UI - SwiftUI Notes #3
  • Section & Grouped - SwiftUI Notes #40
    Section & Grouped - SwiftUI Notes #40
Tags: SwiftUI, SwiftUI Notes
Written by chuotfx

Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift api AppDistribution Asynchronous autolayout basic ios tutorial blog callback ci/cd closure collectionview combine concurrency CoreData Core Location crashlytics darkmode dart dart basic dart tour Declarative decoding delegate deploy fabric fastlane firebase flavor flutter GCD iOS mapview MVVM optional protocol rxswift Swift Swift 5.5 SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Raw String trong 10 phút
  • Dispatch Semaphore trong 10 phút
  • Tổng kết năm 2022
  • KeyPath trong 10 phút – Swift
  • Make color App Flutter
  • Ứng dụng Flutter đầu tiên
  • Cài đặt Flutter SDK & Hello world
  • Coding Conventions – người hùng hay kẻ tội đồ?
  • Giới thiệu về Flutter
  • Tìm hiểu về ngôn ngữ lập trình Dart

You may also like:

  • Tích hợp UIView (UIKit) vào SwiftUI Project - SwiftUI Notes…
    Tích hợp UIView (UIKit) vào SwiftUI Project - SwiftUI Notes #15
  • Creating your UI - SwiftUI Notes #2
    Creating your UI - SwiftUI Notes #2
  • Presenting an Alert - SwiftUI Notes #4
    Presenting an Alert - SwiftUI Notes #4
  • RxDataSource & TableView with Section - RxSwift
    RxDataSource & TableView with Section - RxSwift
  • SwiftUI - Phần 10 : Grid Layout
    SwiftUI - Phần 10 : Grid Layout

Archives

  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (22)
  • Code (4)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (86)
  • RxSwift (37)
  • SwiftUI (76)
  • Tutorials (70)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2023 Fx Studio - All rights reserved.

    Share this ArticleLike this article? Email it to a friend!

    Email sent!