Contents
Chào mừng bạn đến với Fx Studio. Chúng ta lại tiếp tục với series SwiftUI bất tận này. Bạn đã học được cách hiển thị theo dạng List với các Row, tuy nhiên chúng ta còn một cách hiển thị một danh sách nữa. Đó là theo dạng ô lưới, hay dân gian lập trình gọi là Grid Layout. Bạn sẽ tìm hiểu cơ bản về nó trong bài viết này.
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:
(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.)
Grid Layout
Nếu bạn là người tìm hiểu về SwiftUI ngay từ những ngày đầu khi vừa ra mắt. Nó đã tiến hóa lên tới version 3.0 rồi, tuy nhiên vẫn chưa có một đối lượng hay class nào tương tự như UICollectionView trong UIKit. Mặc dù, bạn có List là một đối tượng thay thế cho UITableView và List đáp ứng đầy đủ các tính năng của UITableView. Thậm chí, một số trường hợp thì List trở nên đa năng UITableView rất nhiều.
Còn về UICollectionView hay cách bố trí giao diện dưới dạng các ô lưới, thì SwiftUI chỉ mới cập nhật tại iOS 14 với 2 đối tượng là: LazyVGrid & LazyHGrid.
Còn về cấu tạo và cách hoạt động của LazyVGrid & LazyHGrid có thay thế được UICollectionView hay không, thì đích thân bạn sẽ phải tự trải nghiệm mới biết được.
Trong phạm vi của Fx Studio, mình chỉ tập trung tìm hiểu và chia sẽ lại những kiến thức của SwiftUI cung cấp mà thôi. Có thể bạn sẽ có được một giải pháp đơn giản hơn khi sử dụng các thư viện bên ngoài.
VStack & HStack
Để có được một giao diện như UICollectionView trong UIKit, bạn có rất nhiều cách để tạo ra chúng. Trong đó, cách kết hợp giữa VStack & HStack được xem là đơn giản nhất trong SwiftUI (giai đoạn còn sơ khai).
Bạn xem qua ví dụ code sau:
struct DemoCollectionView: View { var body: some View { VStack { ForEach(0...8, id: \.self) { indexV in HStack { ForEach(0...3, id: \.self) { indexH in Text("\(indexV*4 + indexH)") .font(.title) .foregroundColor(Color.white) .frame(width: 80, height: 80, alignment: .center) .background(Color.accentColor) } } } } } }
Thật đơn giản phải không nào, trong đó:
- VStack được xem là các hàng
- HStack xem là các cột
Chúng kết hợp với 2 vòng ForEach và tạo nên một giao diện khá là giống với UICollectionView. Nội dung của các phần tử trong ô lưới thì là Text đơn giản.
Bạn thử bấm Resume và xem kết quả nhóe!
Mặc dù rất nhanh chóng để bạn có được giao diện theo ý muốn. Tuy nhiên, chúng ta lại khó khăn trong việc:
- Lặp dữ liệu, vị chúng tới 2 vòng ForEach
- Cài đặt giao diện khá thủ công, khi bạn phải tự điều chỉnh và canh kích thước các ô sao cho phù hợp
Cho nên, SwiftUI đã phải giúp bạn rất nhiều khi thêm 2 SwiftUI View mới (LazyVGrid & LazyHGrid) phục vụ cho việc hiển thị nội dung theo Grid Layout.
LazyVGird
LazyVGrid là một SwiftUI View dùng để bố cục giao diện theo Grid Layout và được giới thiệu trong SwiftUI 2.0. Người anh em của chúng là LazyHGrid. Chúng cung cấp cho bạn một giao diện hiển thị kiểu ô lưới (grid) nhưng với một cách tự động và linh hoạt.
Quan trọng nó là nó đơn giản đến bất ngờ!
Chúng sẽ dựa vào dữ liệu bạn cung cấp để bố trí giao diện hiển thị sao cho phù hợp nhất. Việc cài đặt giao diện cho từng ô lưới thì bạn có thể cấu hình một cách tự động và duy nhất một lần. Gọi là GridItems. Khi xuất hiện thì LazyVGrid hay LazyHGrid sẽ dựa vào đó để bố trí giao diện theo dữ liệu.
Cũng khá tương đồng với UICollectionView và FlowLayout của nó.
Với LazyVGrid, bạn sẽ scroll nội dung theo chiều dọc màn hình. Còn bố cục các item sẽ theo hàng ngang (columns).
Create
Để tạo một Grid Layout chúng ta sẽ đi theo các bước lần lượt như sau:
- Bước 1: Chuẩn bị dữ liệu
let data = (1...100).map { "Item \($0)" }
Ví dụ, với một dữ liệu là data
, là một mãng String với nội dung từ 1 tới 100
- Bước 2: Tạo các GridItem, thứ sẽ quyết định Grid của bạn trông như thế nào. Ví dụ: bao nhiêu cột, hàng, kích thước, khoảng cách …
let columns = [ GridItem(.adaptive(minimum: 80)) ]
Tiếp theo, ta có một thuộc tính là columns
với kiểu dữ liệu là Array GridItem. Vì chứa 1 phần tử nên sẽ mong muốn Grid chứa được tối đa nhất các phần tử trên 1 hàng. Kích thước nhỏ nhất của một phần tử là 80 px
.
- Bước 3: Tạo đối tượng LazyVGrid và được bọc với ScrollView. Nội dung bên trong sẽ dùng ForEarch lặp và sinh ra các View đại diện cho các ô trong Grid.
ScrollView { LazyVGrid(columns: columns, spacing: 20) { ForEach(data, id: \.self) { item in Text(item) .foregroundColor(Color.white) .padding(5) .background(Color.purple) } } .padding(.horizontal) } .frame(maxHeight: .infinity)
Trong đó:
- Truyền
columns
cho LazyVGrid để xác định bố cục - ForEach sẽ lặp các phần tử của
data
- Mỗi bước lặp ta sẽ tạo một đối tượng Text
- ScrollView bọc ngoài cùng vì kích thước của nội dung bên trong sẽ lớn hơn kích thước màn hình.
Bấm Live Preview và xem kết quả nhóe!
Columns
Khi bạn muốn số lượng cột cụ thể và xác định, ví dụ như chỉ có 2 hay 3 cột mà thôi. Thì bạn sẽ phải cần thay đổi tham số của thuộc tính columns
. Ví dụ với 3 cột nhóe, code sẽ như thế này.
let columns = [ GridItem(.flexible(minimum: 80)), GridItem(.flexible(minimum: 80)), GridItem(.flexible(minimum: 80)) ]
Chúng ta sẽ khai báo 1 mãng GridItem với 3 phần tử xác định cụ thể với tham số .flexible
. Như vậy mãng columns
cho bao nhiêu phần tử thì sẽ có bấy nhiêu cột hiển thị.
Chỉnh sửa lại LazyVGrid một tí nào!
ScrollView { LazyVGrid(columns: columns, spacing: 20) { ForEach(data, id: \.self) { item in Text(item) .foregroundColor(Color.white) .padding(5) .frame(maxWidth: .infinity) .background(Color.purple) } } .padding(.horizontal) } .frame(maxHeight: .infinity)
Với modifier .frame(maxWidth: .infinity)
cho Text, để giúp bạn thấy được kích thước của các ô trong Grid đẹp hơn. Cuối cùng, bạn bấm Live Preview và cảm nhận tiếp kết quả nha!
Spacing
Tham số spacing
trong hàm khởi tạo của LazyVGrid sẽ là khoảng cách giữa các dòng. Muốn đẹp hơn một tí thì bạn có thể thay đổi lại. Ví dụ mình chỉnh xuống là 8
cho đẹp.
ScrollView { LazyVGrid(columns: columns, spacing: 8) { ForEach(data, id: \.self) { item in Text(item) .foregroundColor(Color.white) .padding(5) .frame(maxWidth: .infinity) .background(Color.purple) } } .padding(.horizontal) } .frame(maxHeight: .infinity)
Và em nó sẽ trông như thế này nè.
Và bạn muốn điều chỉnh khoảng cách giữa các cột lại với nhau thì sẽ phải thêm tham số spacing
ở các phần tử GridItem trong array columns
. Ví dụ như sau:
let columns = [ GridItem(.flexible(minimum: 80), spacing: 0), GridItem(.flexible(minimum: 80), spacing: 0), GridItem(.flexible(minimum: 80), spacing: 0) ]
Kết quả thì trông khá là dị, bấm Live Preview và cảm nhận nhóe!
LazyHGrid
Cũng tương tự như LazyVGrid về mọi mặt. LazyHGrid dùng để bố trí nội dung các ô lưới trong Grid. Với LazyHGrid, bạn sẽ scroll nội dung theo chiều ngang màn hình. Còn bố cục các item sẽ theo hàng dọc (rows).
Columns & Rows hơi lội xộn hỉ. Bạn làm vài cái demo là hiểu liền à, chứ về ngôn từ mình không giải thích hay viết ra cho cụ thể được. Ahihi!
Cơ bản về cách tạo thì khá tương đồng nhau. Bạn xem qua ví dụ code sau sẽ hiểu liền.
struct Demo02GridLayout: View { private var symbols = ["keyboard", "hifispeaker.fill", "printer.fill", "tv.fill", "desktopcomputer", "headphones", "tv.music.note", "mic", "plus.bubble", "video"] private var colors: [Color] = [.yellow, .purple, .green] private var gridItemLayout = [GridItem(.fixed(80), spacing: 8), GridItem(.fixed(80), spacing: 8), GridItem(.fixed(80), spacing: 8)] var body: some View { ScrollView(.horizontal) { LazyHGrid(rows: gridItemLayout, spacing: 8) { ForEach((0...9999), id: \.self) { Image(systemName: symbols[$0 % symbols.count]) .font(.system(size: 30)) .frame(minWidth: 80, maxWidth: .infinity, maxHeight: .infinity) .background(colors[$0 % colors.count]) .cornerRadius(10) } } .padding() } .frame(maxHeight: .infinity) } }
Trong đó:
- Phần dữ liệu bao gồm
symbols
vàcolor
. Là tên các icon và màu sắc của hệ thống - Thuộc tính
gridItemLayout
sẽ quyết định layout cho Grid của bạn. Với 3 phần tử, thì bạn sẽ có 3 hàng cho LazyHGrid hoặc 3 cột cho LazyVGrid. - Vì bạn sẽ scroll theo chiều ngang màn hình. Do đó, cần thêm tham số cho ScrollView là
ScrollView(.horizontal)
- Khởi tạo của LazyHGrid sẽ cần tham số
rows
(với LazyVGrid làcolumns
)
Mọi thứ còn lại thì vẫn như trên. Chúng ta bấm Live Preview và test lại xem nhóe!
Tạm kết
- Hiển thị giao diện kiểu Grid (ô lưới, hay Collection) trong SwiftUI
- Sử dụng hai đối tượng Grid Layout là LazyVGrid & LazyHGrid
- Điều chỉnh các tham số trong Grid Layout
- Thay đổi bố cục với GridItem
Okay! Tới đây, mình xin kết thúc bài viết về Basic Grid Layout 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.
Cảm ơn bạn đã đọc bài viết này!
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
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- 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)