Contents
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 với SwiftUI và chủ đề bài viết này vẫn là điều hướng trong SwiftUI. Đối tượng lần này là TabView. Nó là gì và hoạt động ra sao, thì chúng ta cùng nhau tìm hiểu trong bài viết này nhoé!
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.)
TabView
TabView là một SwiftUI View với cấu tạo tương tự như UITabbarController của UIKit. Cung cấp chọn bạn một giao diện với nhiều màn hình. Các màn hình sẽ ở từng tab và chuyển đổi qua lại giữa các tab để hiển thị các màn hình đó. Đi kèm với bộ cục giao diện như vậy, thì chúng ta có điều hướng đặc trưng của TabView là chuyển đổi qua lại ngang cấp giữa các chức năng.
Create a TabView
Bắt đầu, chúng ta sẽ tìm hiểu việc tạo một TabView thì như thế nào nhoé. Bạn tham khảo đoạn code sau đây để tạo 1 TabView.
TabView { Text("The First Tab") .tabItem { Image(systemName: "1.square.fill") Text("First") } Text("Another Tab") .tabItem { Image(systemName: "2.square.fill") Text("Second") } Text("The Last Tab") .tabItem { Image(systemName: "3.square.fill") Text("Third") } }
Trong đó, bạn sẽ thấy 2 thành phần quan trọng:
- TabView chính là đối tượng chúng ta cần để hiển thị
- Trong TabView chúng ta có các View con của nó. Số lượng các View con sẽ là số lượng tab của TabView
- Tại mỗi View con, bạn thêm modifier
.tabItem
. Đây chính là Tab bar Item trong TabView.
Bạn thử bấm Live Preview và cảm nhận kết quả nha!
Tab Item
Các Tab Item sẽ đi kèm với các View. Nó được tạo ra bằng việc sử dụng modifier .tabItem
. Trong ví dụ code trên như sau:
.tabItem { Image(systemName: "2.square.fill") Text("Second") }
Tạo nên mỗi Tab Item thì bạn có 2 phần:
- Image chính là icon của Tab Item
- Text chính là lable hay title của Tab Item
Có thể có 1 trong 2 cái vẫn được, hoặc cả hai. Với mỗi Tab được chọn, thì Tab Item của nó sẽ được đổi màu sang trạng thái selected
.
Badge
Đây là tính năng được thêm vào trong iOS 15, bạn sử dụng modifier .badge
để hiển thị một Badge Number cho các Tab Item. Với Badge, bạn sẽ dùng nhiều trong việc hiển thị các thông tin từ notification hoặc muốn cho người dùng chú ý tới.
Bạn xem ví dụ code sau:
TabView { Text("The First Tab") .tabItem { Image(systemName: "1.square.fill") Text("First") } .badge(99) Text("Another Tab") .tabItem { Image(systemName: "2.square.fill") Text("Second") } .badge("ahihi") Text("The Last Tab") .tabItem { Image(systemName: "3.square.fill") Text("Third") } }
Bạn sẽ để ý là .badge
sẽ nhận được 2 giá trị với 2 kiểu dữ liệu là Int hoặc String. Bấm Resume và xem kết quả nhoé!
accentColor
Mặc định, khi bạn chọn các Tab Item thì nó sẽ được hiển thị với màu xanh. Để thay đổi màu này bạn sẽ cần sử dụng modifier .accentColor
và truyền tham số màu. Bạn xem qua code Ví dụ sau nhoé!
TabView { // ... } .onAppear() { UITabBar.appearance().barTintColor = .yellow } .accentColor(.yellow)
Trong ví dụ ở trên, bạn sẽ thấy là có sử dụng thêm UITabBar.appearance()
. Tuy nhiên, với vài cách custom với appearance
thì SwiftUI sẽ không hiển thị. Trong đó có accentColor
hay barTintColor
này. Bấm Resume và cảm nhận kết quả nhoé.
Tabbar appearance
Cũng tương tự với việc custom NavigationBar, thì Tabbar cũng sử dụng cách tương tự. Ví dụ code như sau:
.onAppear() { UITabBar.appearance().barTintColor = .yellow UITabBar.appearance().backgroundColor = UIColor.red }
Bạn có thể lợi dùng .onAppear
hoặc hàm init
để tạo appearance và custom các thuộc tính cho Tabbar. Còn trong ví dụ mình chỉ thay đổi giá trị backgroudColor
mà thôi.
Hiding the Tab Bar in a Navigation View
Bạn thử nghĩ sẽ ra sao khi chúng ta đưa TabView vào trong NaviagtionView.
NavigationView { TabView(selection: $selection) { // .... } .navigationTitle("TabView Demo") }
Đúng là với SwiftUI, thì mọi thứ đều có thể xảy ra. Với cách bố cục layout như thế này thì chúng ta sẽ được một kết quả khá là bất ngờ.
NavigationView { TabView { List { ForEach(1...10, id: \.self) { index in NavigationLink(destination: Text("Item #\(index)")) { Text("Item #\(index)") } } } .tabItem { Image(systemName: "1.square.fill") Text("First") } .badge(99) Text("Another Tab") .tabItem { Image(systemName: "2.square.fill") Text("Second") } .badge("ahihi") Text("The Last Tab") .tabItem { Image(systemName: "3.square.fill") Text("Third") } } .onAppear() { UITabBar.appearance().barTintColor = .yellow //UITabBar.appearance().backgroundColor = UIColor.red } .accentColor(.yellow) .navigationTitle("TabView Demo") }
Trong đó:
- Tại một View con của TabView. Ta tạo một danh sách List
- Khi bạn dùng NavigationLink để điều hướng sang màn hình Detail, thì sẽ ẩn đi Tabbar
Vì ta đang sử dụng NavigationView là root
của toàn bộ cấu trúc. Do đó, nó sẽ điều hướng một View mới đè lên toàn bộ TabView. Trải nghiệm lúc này thì bạn sẽ ẩn đi được Tabbar rồi. Tuy nhiên ….
Bạn nên cẩn thận khi thiết kế bố cục các View trong SwiftUI nhoé!
PageView
Với TabView, ta còn một cách điều hướng nữa. Đó chính là bạn scroll từ View ngang qua. Nó khá là giống với PageView bên UIKit. Và bạn có thể lợi dụng nó để thiết kế giao diện cho riêng bạn, với dữ liệu theo từng page nhoé.
Create a PageView
Ta sẽ bắt đầu với một PageView đơn giản. Bạn đặt tên cho View mới này là TutorialView nhoé và xem code tham khảo như sau:
TabView { VStack { Image(systemName: "chevron.left.slash.chevron.right") .resizable() .foregroundColor(Color.purple) .frame(maxWidth: 100.0, maxHeight: 100.0) Text("Coding") .font(.largeTitle) .fontWeight(.bold) .foregroundColor(Color.purple) } VStack { Image(systemName: "network") .resizable() .foregroundColor(Color.purple) .frame(maxWidth: 100.0, maxHeight: 100.0) Text("Sharing") .font(.largeTitle) .fontWeight(.bold) .foregroundColor(Color.purple) } VStack { Image(systemName: "heart.circle.fill") .resizable() .foregroundColor(Color.purple) .frame(maxWidth: 100.0, maxHeight: 100.0) Text("Relax") .font(.largeTitle) .fontWeight(.bold) .foregroundColor(Color.purple) } } .tabViewStyle(.page)
Trong đó, vẫn là cấu trúc với TabView như trên. Nhưng ta sẽ sử dụng tới modifier .tabViewStyle
. Với giá trị truyền vào là .page
. Bấm Live Preview và bạn thử scroll ngang qua nhoé!
Với chế độ style là .page
thì phần Tabbar & Tab item sẽ không xuất hiện. Người dùng sẽ phải điều hướng bằng cách scroll.
Mình sẽ sử dụng lại TutorialView này cho các bài viết sau.
Index
Với kiểu giao diện như thế này, đôi khi bạn sẽ không biết được rằng mình đang ở page thứ mấy. Do đó, bạn cần thêm một việc nữa là hiển thị index
cho từng page. Chúng ta sẽ dùng tới modifier .indexViewStyle
như sau:
TabView { // .... } .tabViewStyle(.page) .indexViewStyle(.page(backgroundDisplayMode: .always))
Change Tab
Phần cuối, ta sẽ tìm hiểu cách thay đổi các Tab hiển thị, tất nhiên là không phải việc người dùng kích chọn ở Tabbar nhoé. Để nâng độ khó, thì chúng ta sẽ tạo các View riêng lẻ từ bên ngoài cho các Tab.
- Các view riêng lẻ như sau.
// Home struct HomeView: View { var body: some View { Text("Home") .font(.largeTitle) } } // Map struct MapView: View { var body: some View { Text("Map") .font(.largeTitle) } } // Videos struct VideosView: View { var body: some View { Text("Videos") .font(.largeTitle) } } // Profile struct ProfileView: View { var body: some View { Text("Profile") .font(.largeTitle) } }
- Tạo một TabView mới với các View con là các View ở trên nhoé
struct MyTabView: View { var body: some View { TabView { HomeView() .tabItem { Image(systemName: "house.fill") Text("Home") } .tag(0) MapView() .tabItem { Image(systemName: "map.fill") Text("Map") } .tag(1) VideosView() .tabItem { Image(systemName: "video.circle.fill") Text("Videos") } .tag(2) ProfileView() .tabItem { Image(systemName: "person.crop.circle") Text("Profile") } .tag(3) } .accentColor(.green) } }
Có một điểm đặc biệt, là ta sẽ sử dụng modifier .tag
để đánh dấu các Tab trong TabView.
Binding
Được xem là cách truyền thống trong SwiftUI với The single source of truth. Bạn cần tạo ra một biến State để quản lý việc hiển thị của Tabbar. Từ đó chúng ta sẽ tiến hành thay đổi giá trị của chúng.
Đầu tiên, ta sẽ thêm một thuộc tính State cho TabView.
@State var selection = 0
Tiếp theo, bạn sẽ thêm tham số cho việc khởi tạo TabView. Bạn truyền thuộc tính $selection
cho tham số selection
của TabView. Tham khảo code ở dưới.
TabView(selection: $selection) { HomeView() .tabItem { Image(systemName: "house.fill") Text("Home") } .tag(0) MapView() .tabItem { Image(systemName: "map.fill") Text("Map") } .tag(1) VideosView() .tabItem { Image(systemName: "video.circle.fill") Text("Videos") } .tag(2) ProfileView() .tabItem { Image(systemName: "person.crop.circle") Text("Profile") } .tag(3) } .accentColor(.green)
Tại các View, bạn tiến hành thêm thuộc tính Binding để ràng buộc dữ liệu với selection
. Và mình sẽ tạo thêm các Button để thực hiện việc đổi Tab từ các View con đó. Bạn tham khảo HomeView và áp dụng hết cho các View còn lại nhoé.
struct HomeView: View { @Binding var selection: Int var body: some View { VStack { Text("Home") .font(.largeTitle) Divider() HStack { Button { selection = 0 } label: { Text("Tab #1") } Button { selection = 1 } label: { Text("Tab #2") } Button { selection = 2 } label: { Text("Tab #3") } Button { selection = 3 } label: { Text("Tab #4") } } .padding() } } }
Cuối cùng, bạn cập nhật lại các hàm khởi tạo các View con trong TabView nhoé.
TabView(selection: $selection) { HomeView(selection: $selection) .tabItem { Image(systemName: "house.fill") Text("Home") } .tag(0) MapView(selection: $selection) .tabItem { Image(systemName: "map.fill") Text("Map") } .tag(1) VideosView(selection: $selection) .tabItem { Image(systemName: "video.circle.fill") Text("Videos") } .tag(2) ProfileView(selection: $selection) .tabItem { Image(systemName: "person.crop.circle") Text("Profile") } .tag(3) } .accentColor(.green)
Mọi thứ đã ổn rồi, bạn Live Preview và test lại nha.
@SceneStorage
Bạn sẽ dùng tới một đối tượng tương tự như với @State. Đó là @SceneStorage, nó vừa có khả năng như một State và vừa có thể lưu trữ ngắn hạn giá trị trong phạm vi của Scene.
Mỗi khi bạn build lại ứng dụng thì sẽ mất đi và cập nhật lại từ đầu. Nên hoàn toàn thích hợp trong hoàn cảnh này.
Tại sao phải sử dụng tới @SceneStorage làm gì? Vì:
- Bạn sẽ không cần phải lúc nào cũng truyền các thuộc tính State đi khắp các View.
- Nó giống như một phiên bản Singleton mà bạn có thể triệu hồi ở bất kì đâu trong toàn bộ ứng dụng.
Về lại với demo, bạn chỉ cần thay khai báo selection
với kiểu @State
bằng @SceneStorage
thôi.
@SceneStorage("selectedTab") var selection = 0
Trong đó, selectedTab
là key
của biến selection
với kiểu @SceneStorage
. Giúp bạn có thể sử dụng nó ở các View khác. Và cũng cùng thuộc nguyên lý The single source of truth, nên lúc này bạn bấm Live Preview thì nó vẫn hoạt động. Các View vẫn ràng buộc dữ liệu với các biến @SceneStorage và vẫn sẽ tự động cập nhật lại giao diện khi dữ liệu thay đổi.
Tuy nhiên, ta sẽ tuỳ chỉnh một chút ở các View con như sau:
struct HomeView: View { //@Binding var selection: Int @SceneStorage("selectedTab") var selection = 0 var body: some View { VStack { Text("Home") .font(.largeTitle) Divider() HStack { Button { selection = 0 } label: { Text("Tab #1") } Button { selection = 1 } label: { Text("Tab #2") } Button { selection = 2 } label: { Text("Tab #3") } Button { selection = 3 } label: { Text("Tab #4") } } .padding() } } }
Thay @Binding
bằng @SceneStorage
và chú ý đúng key
là được. Cuối cùng bạn cập nhật lại hàm khởi tạo các View con là ổn.
TabView(selection: $selection) { HomeView() .tabItem { Image(systemName: "house.fill") Text("Home") } .tag(0) MapView() .tabItem { Image(systemName: "map.fill") Text("Map") } .tag(1) VideosView() .tabItem { Image(systemName: "video.circle.fill") Text("Videos") } .tag(2) ProfileView() .tabItem { Image(systemName: "person.crop.circle") Text("Profile") } .tag(3) } .accentColor(.green)
Code của bạn sẽ trông gọn & đẹp hơn rồi. Bấm Live Preview và cảm nhận kết quả nhoé!
Tạm kết
- Tìm hiểu về TabView và cách điều hướng của nó
- Thực hiện custom và thay đổi các giá trị cơ bản của Tabbar & Tab Item
- Tạo một PageView bằng cách thay đổi style của TabView
- Thay đổi hiện thị của các Tab bằng Binding & SceneStorage
Okay! Tới đây, mình xin kết thúc bài viết về TabView & PageView 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!
Related Posts:
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)