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 September 29, 2021

Custom TabBar – SwiftUI Notes #49

SwiftUI

Contents

  • Chuẩn bị
  • Custom TabBar
  • 1. Setup Custom View
    • 1.1. Layout
    • 1.2. GeometryReader
  • 2. Layout TabBar
  • 3. Create TabItem
  • 4. Add TabItems
    • 4.1. Add More Items
    • 4.2. SafeArea
    • 4.3. Item padding
  • 5. Plus Button
  • 6. Setup TabBar Router
  • 7. Config Router
  • 8. Implement Router in TabItem
  • 9. Action for Plus Button
    • 9.1. Call back
    • 9.2. Modal View
    • 9.3. Show Popup
  • 10. Change Tab from Content Views
  • Tạm kết

Chào mừng bạn đến với Fx Studio. Chúng ta lại tiếp tục với hành trình bất tận của series SwiftUI. Chủ đề bài viết này là Custom TabBar. Mặc dù, chúng ta đã tìm hiểu về TabView và sử dụng TabBar mặc định của SwiftUI cung cấp. Tuy nhiên, với các giao diện phức tạp thì nó lại không đáp ứng được. Và bên cạnh đó, giao diện với một TabBar làm trung tâm của ứng dụng, thì cũng khá là phổ biến trong hầu hết các ứng dụng (như: Youtube, Facebook …). Vì vậy,

Bạn phải custom được một TabBar và đầy đủ các tính năng như một TabView (hay UITabbarController của UIKit).

Nếu bạn chưa biết về TabView & UITabbarController thì có thể tìm đọc lại ở các bài viết sau:

    • TabView & PageView
    • Tabbar Controller

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

Custom TabBar

Để cho bạn dễ hình dùng chúng ta sẽ làm gì, thì kết quả cuối cùng của chúng ta sẽ như thế này.

Custom Tabbar SwiftUI

Đây cũng được xem là một TabBar điển hình trong mọi ứng dụng. Bạn có thể sử dụng các Tab của nó để chuyển đổi sang các màn hình tương ứng. Các TabItem vẫn tương tự như TabItem của TabView, nhằm để tạo một trải nghiệm quen thuộc với người dùng.

Thường sẽ đi kèm với một Button chính giữa. Nó sẽ có một chức năng khác và điều hướng khác so với các TabItem còn lại. Ví dụ, bạn sẽ present một View lên và thực hiện các chứng năng như: chụp ảnh, record video, post bài viết mới …

Cũng như với UIKit & UITabbarController, việc tạo được giao diện như trên thì cũng rất khó khăn. Và với SwiftUI, bạn cũng phải làm được công việc này thôi.

Số kiếp dev cùi là vậy rồi. Ahuhu!

Chúng ta sẽ tiến hành tạo một Custom View theo các phần công việc sau đây. Và bài viết này, sẽ tập trung hoàn toàn về thực hành và sẽ không giải thích lý thuyết nhiều.

1. Setup Custom View

1.1. Layout

Chúng ta sẽ cần tạo ra một khung chứa với các bố cục layout cơ bản đầu tiên cho TabBar. Bạn tạo mới một SwiftUI View và mình tạm đặt tên là FxTabBar nhoé. Code ví dụ ban đầu như sau:

struct FxTabBar: View {
    var body: some View {
        VStack {
            Spacer()
            // Contents
            Text("Home")
            Spacer()
            // Tabbar
        }
    }
}

Ta lựa chọn VStack để sắp xếp nội dung theo các dòng. Trong đó, tách biệt ra 2 phần chính:

  • Contents: chính là phần hiển thị nội dung của các View trong TabBar (như: HomeView, MapView …)
  • TabBar: sẽ chứa các TabBarItem

Các Spacer giúp ép các nội dung đúng với các vị trí mà chúng ta mong muốn. Trong trường hợp này thì TabBar sẽ luôn được ở phía dưới cùng của màn hình.

1.2. GeometryReader

Chúng ta cần phải biết được kích thước màn hình đang sử dụng để tính toán và bố trí kích thước của các TabBar và cá TabItem. Do đó, bạn sẽ cần sử dụng tới đối tượng GeometryReader.

Ta sẽ bọc toàn bộ View lại trong GeometryReader nhoé.

var body: some View {
    GeometryReader { geometry in
        VStack {
            Spacer()
            // Contents
            Text("Home")
            Spacer()
            // Tabbar
        }
    }
}

Trong đó, tham số geometry sẽ được sử dụng để lấy các kích thước width & height của màn hình.

2. Layout TabBar

Vì bố cục của các TabItem sẽ được xếp hàng ngang với nhau. Do đó, root của phần View cho TabBar sẽ là một HStack. Bạn sẽ thêm tiếp vào phần //Tabbar trong đoạn code trên.

var body: some View {
    GeometryReader { geometry in
        VStack {
            Spacer()
            // Contents
            Text("Home")
            Spacer()
            // Tabbar
            HStack {
                
            }
        }
    }
}

Tiếp theo, ta sẽ tính toán và đưa ra kích thước cho phần TabBar, bạn tham khảo tiếp code nhoé.

HStack {
     
 }
 .frame(width: geometry.size.width, height: geometry.size.height/8)
 .background(Color("TabBarBackground").shadow(radius: 2))

Trong đó:

  • Sử dụng geometry để lấy các kích thước width & height. Tuỳ ý hoặc theo thiết kế riêng của bản nhoé.
  • Tạo thêm phần bóng đổ cho tách biệt giữa nội dung và TabBar
  • Màu sắc thì bạn sẽ tạo các màu sắc ở assets nhoé. Trong ví dụ trên, mình tạo sẵn một TabBarBackgroud rồi

Tiếp theo, ta thử thêm View cho một TabItem xem ra sao, tham khảo đoạn code sau.

var body: some View {
    GeometryReader { geometry in
        VStack {
            Spacer()
            // Contents
            Text("Home")
            Spacer()
            // Tabbar
            HStack {
                // Home TabItem
                VStack {
                         Image(systemName: "homekit")
                             .resizable()
                             .aspectRatio(contentMode: .fit)
                             .frame(width: geometry.size.width/5, height: geometry.size.height/28)
                             .padding(.top, 10)
                         Text("Home")
                             .font(.footnote)
                         Spacer()
                     }
            }
            .frame(width: geometry.size.width, height: geometry.size.height/8)
            .background(Color("TabBarBackground").shadow(radius: 2))
        }
    }
}

Bấm Resume và xem kết quả nhoé!

3. Create TabItem

Qua trên, ta có một TabItem được viết vào trong cùng với TabBar. Nhưng số lượng code và dòng code nhiều quá. Nếu ta có tới 4 hay 5 TabItem thì quá nhiều code. Vì vậy, cần tách code và tạo một Custom View riêng cho phần này.

Bạn tạo mới một SwiftUI View nhoé và đặt tên là FxTabItem. Tham khảo code như sau:

struct FxTabItem: View {
     
     let width, height: CGFloat
     let systemIconName, tabName: String
     
     
     var body: some View {
         VStack {
             Image(systemName: systemIconName)
                 .resizable()
                 .aspectRatio(contentMode: .fit)
                 .frame(width: width, height: height)
                 .padding(.top, 10)
             Text(tabName)
                 .font(.footnote)
             Spacer()
         }
     }
 }

Về bản chất, nó là code phần Item Home mà bạn đã thêm ở trên. Chúng ta tách code qua và thêm các thuộc tính cần thiết. Như là width, height, systemIconName, tabName.

Bạn sang FxTabBar, tạo một đối tượng FxTabItem thay cho đoạn code dài dòng trên nhoé.

HStack {
   FxTabItem(width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "homekit", tabName: "Home")
}

Tại sao là width/5 và height/28 , thì chúng ta sẽ sang phần tiếp theo nhoé!

4. Add TabItems

4.1. Add More Items

Với demo của chúng ta sẽ có tới 4 Tab chính và 1 Push Button. Trước tiên, ta sẽ thêm 5 Item để cho bố cục được ổn định trước nhoé.

HStack {
   FxTabItem(width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "homekit", tabName: "Home")
   FxTabItem(width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "map.fill", tabName: "Map")
   FxTabItem(width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "plus", tabName: "Add")
   FxTabItem(width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "video.circle.fill", tabName: "Videos")
   FxTabItem(width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "person.crop.circle", tabName: "Profile")
                     
}

Bạn có thể thiết kế tuỳ ý với giao diện riêng của bạn nhoé. Sau đó, bạn bấm Resume để xem kết quả như thế nào rồi.

Custom Tabbar

Tới đây bạn sẽ thấy là chúng ta có tổng số items là 5, do đó bạn cần width là geometry.size.width/5. Còn với height thì tạm thời ta tính là geometry.size.height/28.

4.2. SafeArea

Bạn cũng để ý ra là có 1 khoảng trắng ở dưới cùng. Nó cách TabBar chúng ta ra. Đó chính là phần SafeArea khả dụng. Nên phần phía dưới và tai thỏ của iPhone, thì sẽ không hiển thị được. Nhiệm vụ tiếp theo là bạn sẽ phải thêm modifier để cho View bỏ qua phần phía dưới của SafeArea nhoé.

GeometryReader { geometry in
     VStack {
         //...
     }
     .edgesIgnoringSafeArea(.bottom)
 }

Bạn chỉ cần sử dụng modifier .edgesIgnoringSafeArea(.bottom) là xong. Và chú ý đặt cho đúng View nào, trong ví dụ của chúng là sẽ là VStack con của GeometryReader.

Bấm Resume và xem kết quả nha.

Nhìn ngon rồi đó!

4.3. Item padding

Thêm một chút khoảng cách cho các TabItem ở trong TabBar để được đẹp hơn. Vì bạn sẽ thấy chúng nó dính mép với nhau. Tuy cập tới FxTabItem và thêm modifier sau nhoé.

struct FxTabItem: View {
    //....
    var body: some View {
        VStack {
                        //....
        }
        .padding(.horizontal, -4)
    }
}

Với .padding(.horizontal, -4) SwiftUI sẽ cách đều 2 bên của View. Mọi thứ sẽ trông hài hoà thêm rồi.

Custom Tabbar

5. Plus Button

Tiếp theo, bạn sẽ làm lại cái nút chính giữa ( + ) thành một Button mới. Mình tạo một custom View mới và đặt tên là FxPlusTabBarButton. Về cấu trúc thì tương tự như FxTabItem. Bạn tham khảo code nhoé.

struct FxPlusTabBarButton: View {
    let width, height: CGFloat
    let systemIconName, tabName: String
    
    var body: some View {
        ZStack {
            Circle()
                .foregroundColor(.white)
                .frame(width: width, height: height)
                .shadow(radius: 4)
            Image(systemName: systemIconName)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: width-6 , height: height-6)
                .foregroundColor(Color("PlusButtonColor"))
        }
        .padding(.horizontal, -4)
    }
}

Trong đó,

  • Sử dụng ZStack thay thế cho VStack. Vì cần giao diện xếp chồng lên với nhau.
  • Có 2 phần là Circle là background và Image là icon.
  • .foregroundColor(Color("PlusButtonColor")) mình define nó ở trong assets nhoé!

Sau đó, bạn tiến hành thay thế trong TabBar chung bằng đối tượng FxPlusTabBarButton nha.

HStack {
    // ...
    FxPlusTabBarButton(width: geometry.size.width/7, height: geometry.size.width/7, systemIconName: "plus.circle.fill", tabName: "plush")
      .offset(y: -geometry.size.height/8/2)
    // ...
}

Vì nó cần ở cao hơn so với các TabItem còn lại, nên ta cần thêm modifier .offset và tham số cho y . Còn cao bao nhiêu là tuỳ bạn nhoé.

Bấm Resume và xem kết quả tiếp nha.

6. Setup TabBar Router

Xem như là bạn đã cơ bản xong phần giao diện cho TabBar và các TabItem & Plus Button rồi. Bây giờ, chúng ta sẽ bắt tay vào việc chính là điều hướng (router).

Bắt đầu, ta cần một class để quản lý việc điều hướng. Bạn tham khảo code sau nhoé.

class TabBarRouter: ObservableObject {
}

Sử dụng Protocol ObservableObject để biến đối tượng của class TabBarRouter thành 1 The single source of truth. Đó là đặc trưng trong SwiftUI.

Tiếp theo, ta khai báo thêm một enum để định nghĩa các màn hình được sử dụng cho Contents View, ví dụ như sau.

enum Page {
    case home
    case map
    case videos
    case profile
    case plus
}

Enum này sẽ định nghĩa các Page mà bạn sẽ sử dụng trong TabBar. Tiếp theo, để biết được sự thay đổi diễn ra khi bạn điều hướng, hay bạn muốn di chuyển tới Page nào đó. Ta cần thêm một thuộc tính nữa cho TabBarRouter.

class TabBarRouter: ObservableObject {
    @Published var currentPage: Page = .home
}

Với @Published, giúp ta ràng buộc giao diện và dữ liệu với nhau. Khi currentPage thay đổi giá trị thì kéo theo giao diện biến đổi theo.

7. Config Router

Tiếp theo là phần việc cấu hình dữ liệu & giao diện liên kết với nhau. Vẫn là nguyên tắc The single source of truth của SwiftUI. Ta sẽ phải tạo ra một thuộc tính đóng vai trò là trung tâm. Bạn khai báo thêm một thuộc tính như vậy cho FxTabBar nhoé.

@StateObject var tabbarRouter = TabBarRouter()

tabbarRouter đảm nhận nhiệm vụ điều hướng chính trong TabBar, vì là kiểu tham chiếu (class type) nên sẽ cần khai báo với @StateObject. Ta tiếp tục thêm một thuộc tính nữa cho giao diện.

@ViewBuilder var contentView: some View {
    switch tabbarRouter.currentPage {
    case .home:
        HomeView()
    case .map:
        MapView()
    case .videos:
        VideosView()
    case .profile:
        ProfileView()
    case .plus:
        EmptyView()
    }
}

Trong đó:

  • contentView là thuộc tính sẽ biến đổi theo dữ liệu (ở đây là tabbarRouter.currentPage). Kiểu dữ liệu của nó là some View, nghĩa là View nào cũng chơi được.
  • Lựa chọn switch case để duyệt qua tất cả các giá trị tương ứng.
  • @ViewBuilder để đảm bảo việc kết hợp với các SwiftUI View khác và xác định nó là một View động.

Cuối cùng, bạn kết hợp contentView vào body nhé.

var body: some View {
    GeometryReader { geometry in
        VStack {
            Spacer()
            // Contents
            contentView
            Spacer()
            // Tabbar
            HStack {
                // ....   
            }
            .frame(width: geometry.size.width, height: geometry.size.height/8)
            .background(Color("TabBarBackground").shadow(radius: 2))
        }
        .edgesIgnoringSafeArea(.bottom)
    }
}

Đơn giản phải không nào. Ahihi!

8. Implement Router in TabItem

Chúng ta đã xong phần cấu hình cho TabBar với router. Tiếp đến, ta sẽ tiếp tục cấu hình router cho các TabItem. Bạn mở FxTabItem và thêm các thuộc tính sau:

@ObservedObject var tabbarRouter: TabBarRouter
let assignedPage: Page

Trong đó:

  • tabbarRouter sẽ lắng nghe trực tiếp tới thuộc tính chính bên TabBar. Hay Binding dữ liệu với nhau. Bên này thay đổi thì kéo theo bên kia thay đổi theo và ngược lại.
  • assignedPage xác định Page hiện tại mà TabItem được chỉ định.

Tiếp theo, bạn cần sự kiện để việc chọn Tab xảy ra trên TabItem. Chúng ta sử dụng modifier onTapGesture. Và gán lại giá trị cho currentPage.

VStack {
    // ....
  }
  .onTapGesture {
      tabbarRouter.currentPage = assignedPage
  }

Muốn biết Tab chúng ta có đang được chọn hay không, thì bạn cần so sánh giữa currentPage & assignedPage. Từ đó, sẽ quyết định màu sắc khi Tab được chọn.

VStack {
   // ....
}
.padding(.horizontal, -4)
.foregroundColor(tabbarRouter.currentPage == assignedPage ? Color("TabBarHighlight") : .gray)
.onTapGesture {
    tabbarRouter.currentPage = assignedPage
}

Sau khi xong cấu hình bên TabItem, bạn sẽ cần cập nhật lại việc khởi tạo các FxTabItem ở TabBar.

HStack {
   FxTabItem(width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "homekit", tabName: "Home", tabbarRouter: tabbarRouter, assignedPage: .home)
   FxTabItem(width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "map.fill", tabName: "Map", tabbarRouter: tabbarRouter, assignedPage: .map)
   FxPlusTabBarButton(width: geometry.size.width/7, height: geometry.size.width/7, systemIconName: "plus.circle.fill", tabName: "plush")
                        .offset(y: -geometry.size.height/8/2)
                    FxTabItem(width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "video.circle.fill", tabName: "Videos", tabbarRouter: tabbarRouter, assignedPage: .videos)
   FxTabItem(width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "person.crop.circle", tabName: "Profile", tabbarRouter: tabbarRouter, assignedPage: .profile)
}

Ổn rồi đó, bạn bấm Live Preview và test lại luôn nhoé!

Custom Tabbar

Tới đây, bạn đã cơ bản xong việc Custom TabBar rồi đó. Hai phần dưới là option thêm cho thêm tính năng đẹp hơn thôi.

9. Action for Plus Button

Ta vẫn còn xử lý sự kiện Plus Button nữa. Vì lý do sẽ điều hướng theo cách khác và nó sẽ làm các tác vụ đặc trưng. Với yêu cầu ban đầu, chúng ta sẽ present lên một Modal View.

9.1. Call back

Sẽ có rất nhiều kiểu để bạn lấy sự kiện từ nó và mình sẽ chọn phương pháp đơn giản nhất là call back. Bạn tham khảo code sau cho FxPlusBarButton.

struct FxPlusTabBarButton: View {
    let width, height: CGFloat
    let systemIconName, tabName: String
    
    var action: () -> Void
    
    var body: some View {
        ZStack {
            Circle()
                .foregroundColor(.white)
                .frame(width: width, height: height)
                .shadow(radius: 4)
            Image(systemName: systemIconName)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: width-6 , height: height-6)
                .foregroundColor(Color("PlusButtonColor"))
        }
        .padding(.horizontal, -4)
        .onTapGesture {
            action()
        }
    }
}

Trong đó:

  • action là một thuộc tính được thêm vào. Với kiểu là closure. Bạn sẽ truyền một function cho action lúc khai báo một đối tượng FxPlusBarButton.
  • Sử dụng modifier .onTapGesture với mục đích triệu hồi action ở trong. Đó là sự kiện người dùng khi táp vào Push Button.

9.2. Modal View

Tiết kiệm thời gian thì mình đã tạo sẵn một Custom View cho Modal View rồi. Bạn tham khảo code của nó nhoé.

struct NewPostView: View {
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        VStack {
            Text("Add a new post")
                .font(.largeTitle)
            Divider()
            Button {
                dismiss()
            } label: {
                Text("Dismiss")
            }

        }
    }
}

Chi tiết phần này không có gì phức tạp hết. Bạn sử dụng dismiss để ẩn đi Modal View. Nó là tính năng mới trên iOS 15 nhoé.

9.3. Show Popup

Tới phần liên kết dữ liệu và giao diện trong TabBar để hiển thị Modal View của Tabbar. Bạn sẽ thêm một thuộc tính như sau.

@State var isShowPopUp = false

Vẫn là nguyên tắc The single source of truth. Thuộc tính isShowPopUp sẽ quản lý trạng thái của Modal View ẩn hiện. Tiếp theo, bạn thêm 1 function với mục đích cập nhật lại giá trị của isShowPopUp.

func showPopUp() {
    isShowPopUp = true
}

Bạn kết hợp modifier .sheet cho View root của TabBar, để present Modal View.

VStack {
    //...
}
.edgesIgnoringSafeArea(.bottom)
.sheet(isPresented: $isShowPopUp) {
    // Dismiss
} content: {
    NewPostView()
}

Cuối cùng, bạn cập nhật lại khởi tạo của FxPlusBarButton với tham số action chính là hàm showPopUp nhoé.

FxPlusTabBarButton(width: geometry.size.width/7, height: geometry.size.width/7, systemIconName: "plus.circle.fill", tabName: "plush", action: showPopUp)

Mọi việc đã xong. Bạn bấm Live Preview và test nhoé.

10. Change Tab from Content Views

Tối ưu hơn nữa, bạn có thể thay đổi Contents View của Tabbar từ chính các Contents View của nó hoặc các child content view. Công việc này sẽ giúp chúng ta rất nhiều về sau, nó giúp cho việc điều hướng đơn giản hơn.

Bắt đầu, bạn sẽ phải tạo thêm các EnvironmentObject ở các Content View. Mình sử dụng HomeView làm ví dụ nhoé.

@EnvironmentObject var tabbarRouter: TabBarRouter

Kiểu dữ liệu của EnvironmentObject là TabBarRouter và ta sẽ truyền đối tượng là thuộc tính tabbarRouter của TabBar. Bạn cập nhật lại các hàm thay đổi tab nha.

HStack {
    Button {
        tabbarRouter.currentPage = .home
    } label: { Text("Tab #1")  }

    Button {
        tabbarRouter.currentPage = .map
    } label: { Text("Tab #2") }
    
    Button {
        tabbarRouter.currentPage = .videos
    } label: { Text("Tab #3") }
    
    Button {
        tabbarRouter.currentPage = .profile
    } label: { Text("Tab #4") }
}
.padding()

Vẫn là nguyên tắc The single source of truth, khi tại các View con thay đổi giá trị, thì nguồn sẽ được cập nhật. Và tất cả sẽ được theo đổi theo. Cuối cùng, bạn truyền giá trị biến môi trường cho các Contents View được khởi tạo trong contentView của TabBar nhoé.

@ViewBuilder var contentView: some View {
    switch tabbarRouter.currentPage {
    case .home:
        HomeView()
            .environmentObject(tabbarRouter)
    case .map:
        MapView()
            .environmentObject(tabbarRouter)
    case .videos:
        VideosView()
            .environmentObject(tabbarRouter)
    case .profile:
        ProfileView()
            .environmentObject(tabbarRouter)
    case .plus:
        EmptyView()
    }
}

Sử dụng modifier .environmentObject và giá trị truyền vào là tabbarRouter. Giá trị này sẽ tự động gán vào cho thuộc tính @EnvironmentObject trong mỗi Content View.

Bạn bấm Live Preview và test lại lần cuối nhoé!

Tạm kết

Qua trên, ta có các bước cơ bản để tạo ra một TabBar với phong cách riêng của bạn. Cách bạn cấu hình & điều hướng trong đó. Từ đó, bạn sẽ cách hình dùng để custom các giao diện phức tạp hơn nhiều. Và bạn có thể xem lại demo qua video dưới đây.

(Video hướng dẫn Custom Tabbar.)

Okay! Tới đây, mình xin kết thúc bài viết về Custom Tabbar 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!

FacebookTweetPinYummlyLinkedInPrintEmailShares27

Related Posts:

  • Tích hợp UIView (UIKit) vào SwiftUI Project - SwiftUI Notes #15
    Tích hợp UIView (UIKit) vào SwiftUI Project - SwiftUI Notes…
  • RxSwift vs. UIKit – Tạo Model với Custom Observable
    RxSwift vs. UIKit – Tạo Model với Custom Observable
  • Updating UI - SwiftUI Notes #3
    Updating UI - SwiftUI Notes #3
  • Presenting an Alert - SwiftUI Notes #4
    Presenting an Alert - SwiftUI Notes #4
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:

  • Reusable View - SwiftUI Notes #10
    Reusable View - SwiftUI Notes #10
  • Tích hợp SwiftUI vào UIKit Project - SwiftUI Notes #13
    Tích hợp SwiftUI vào UIKit Project - SwiftUI Notes #13
  • Presenting an Alert - SwiftUI Notes #4
    Presenting an Alert - SwiftUI Notes #4
  • Creating your UI - SwiftUI Notes #2
    Creating your UI - SwiftUI Notes #2
  • Extracting subview - SwiftUI Notes #5
    Extracting subview - SwiftUI Notes #5

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!