CustomTabBar 自定义选项卡视图

1. 用到的技术点

  1. Generics 泛型

  2. ViewBuilder 视图构造器

  3. PreferenceKey 偏好设置

  4. MatchedGeometryEffect 几何效果

2. 创建枚举选项卡项散列,TabBarItem.swift

Swift 复制代码
import Foundation
import SwiftUI

//struct TabBarItem: Hashable{
//    let iconName: String
//    let title: String
//    let color: Color
//}

///枚举选项卡项散列
enum TabBarItem: Hashable{
    case home, favorites, profile, messages
    
    var iconName: String{
        switch self {
        case .home: return "house"
        case .favorites: return "heart"
        case .profile:   return "person"
        case .messages:  return "message"
        }
    }
    
    var title: String{
        switch self {
        case .home: return "Home"
        case .favorites: return "Favorites"
        case .profile:   return "Profile"
        case .messages:  return "Messages"
        }
    }
    
    var color: Color{
        switch self {
        case .home: return Color.red
        case .favorites: return Color.blue
        case .profile:   return Color.green
        case .messages:  return Color.orange
        }
    }
}

3. 创建选项卡偏好设置 TabBarItemsPreferenceKey.swift

Swift 复制代码
import Foundation
import SwiftUI

/// 选项卡项偏好设置
struct TabBarItemsPreferenceKey: PreferenceKey{
    static var defaultValue: [TabBarItem] = []
    
    static func reduce(value: inout [TabBarItem], nextValue: () -> [TabBarItem]) {
        value += nextValue()
    }
}

/// 选项卡项视图修饰符
struct TabBarItemViewModifer: ViewModifier{
    let tab: TabBarItem
    @Binding var selection: TabBarItem
    
    func body(content: Content) -> some View {
        content
            .opacity(selection == tab ? 1.0 : 0.0)
            .preference(key: TabBarItemsPreferenceKey.self, value: [tab])
    }
}

extension View{
    /// 选项卡项视图修饰符
    func tabBarItem(tab: TabBarItem, selection: Binding<TabBarItem>) -> some View{
        modifier(TabBarItemViewModifer(tab: tab, selection: selection))
    }
}

4. 创建自定义选项卡视图 CustomTabBarView.swift

Swift 复制代码
import SwiftUI

/// 自定义选项卡视图
struct CustomTabBarView: View {
    let tabs: [TabBarItem]
    @Binding var selection: TabBarItem
    @Namespace private var namespace
    @State var localSelection: TabBarItem
    
    var body: some View {
        //tabBarVersion1
        tabBarVersion2
            .onChange(of: selection) { value in
                withAnimation(.easeInOut) {
                    localSelection = value
                }
            }
    }
}

extension CustomTabBarView{
    /// 自定义 tabitem 布局
    private func tabView1(tab: TabBarItem) -> some View{
        VStack {
            Image(systemName: tab.iconName)
                .font(.subheadline)
            Text(tab.title)
                .font(.system(size: 12, weight: .semibold, design: .rounded))
        }
        .foregroundColor(localSelection == tab ? tab.color : Color.gray)
        .padding(.vertical, 8)
        .frame(maxWidth: .infinity)
        .background(localSelection == tab ? tab.color.opacity(0.2) : Color.clear)
        .cornerRadius(10)
    }
    
    /// 选项卡版本1
    private var tabBarVersion1: some View{
        HStack {
            ForEach(tabs, id: \.self) { tab in
                tabView1(tab: tab)
                    .onTapGesture {
                        switchToTab(tab: tab)
                    }
            }
        }
        .padding(6)
        .background(Color.white.ignoresSafeArea(edges: .bottom))
    }
    
    /// 切换选项卡
    private func switchToTab(tab: TabBarItem){
        selection = tab
    }
}

extension CustomTabBarView{
    /// 自定义 tabitem 布局 2
    private func tabView2(tab: TabBarItem) -> some View{
        VStack {
            Image(systemName: tab.iconName)
                .font(.subheadline)
            Text(tab.title)
                .font(.system(size: 12, weight: .semibold, design: .rounded))
        }
        .foregroundColor(localSelection == tab ? tab.color : Color.gray)
        .padding(.vertical, 8)
        .frame(maxWidth: .infinity)
        .background(
            ZStack {
                if localSelection == tab{
                    RoundedRectangle(cornerRadius: 10)
                        .fill(tab.color.opacity(0.2))
                        .matchedGeometryEffect(id: "background_rectangle", in: namespace)
                }
            }
        )
    }
    
    /// 选项卡版本 2
    private var tabBarVersion2: some View{
        HStack {
            ForEach(tabs, id: \.self) { tab in
                tabView2(tab: tab)
                    .onTapGesture {
                        switchToTab(tab: tab)
                    }
            }
        }
        .padding(6)
        .background(Color.white.ignoresSafeArea(edges: .bottom))
        .cornerRadius(10)
        .shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 5)
        .padding(.horizontal)
    }
}

struct CustomTabBarView_Previews: PreviewProvider {
    static let tabs: [TabBarItem] = [.home, .favorites, .profile]
    
    static var previews: some View {
        VStack {
            Spacer()
            CustomTabBarView(tabs: tabs, selection: .constant(tabs.first!), localSelection: tabs.first!)
        }
    }
}

5. 创建自定义选项卡容器视图 CustomTabBarContainerView.swift

Swift 复制代码
import SwiftUI

/// 自定义选项卡容器视图
struct CustomTabBarContainerView<Content: View>: View {
    @Binding var selection: TabBarItem
    let content: Content
    @State private var tabs: [TabBarItem] = []
    
    init(selection: Binding<TabBarItem>, @ViewBuilder content: () -> Content){
        self._selection = selection
        self.content = content()
    }
    
    var body: some View {
        ZStack(alignment: .bottom) {
            content
                .ignoresSafeArea()
            CustomTabBarView(tabs: tabs, selection: $selection, localSelection: selection)
        }
        .onPreferenceChange(TabBarItemsPreferenceKey.self) { value in
            tabs = value
        }
    }
}

struct CustomTabBarContainerView_Previews: PreviewProvider {
    static let tabs: [TabBarItem] = [ .home, .favorites, .profile]
    
    static var previews: some View {
        CustomTabBarContainerView(selection: .constant(tabs.first!)) {
            Color.red
        }
    }
}

6. 创建应用选项卡视图 AppTabBarView.swift

Swift 复制代码
import SwiftUI

// Generics      泛型
// ViewBuilder   视图构造器
// PreferenceKey 偏好设置
// MatchedGeometryEffect 几何效果

/// 应用选项卡视图
struct AppTabBarView: View {
    @State private var selection: String = "Home"
    @State private var tabSelection: TabBarItem = .home
                       
    var body: some View {
        /// 默认系统的 TabView
        // defaultTabView
        /// 自定义 TabView
        customTabView
    }
}

extension AppTabBarView{
    /// 默认系统的 TabView
    private var defaultTabView: some View{
        TabView(selection: $selection) {
            Color.red
                .ignoresSafeArea(edges: .top)
                .tabItem {
                    Image(systemName: "house")
                    Text("Home")
                }
            Color.blue
                .ignoresSafeArea(edges: .top)
                .tabItem {
                    Image(systemName: "heart")
                    Text("Favorites")
                }
            Color.orange
                .ignoresSafeArea(edges: .top)
                .tabItem {
                    Image(systemName: "person")
                    Text("Profile")
                }
        }
    }
    
    /// 自定义 TabView
    private var customTabView: some View{
        CustomTabBarContainerView(selection: $tabSelection) {
            Color.red
                .tabBarItem(tab: .home, selection: $tabSelection)
            
            Color.blue
                .tabBarItem(tab: .favorites, selection: $tabSelection)
            
            Color.green
                .tabBarItem(tab: .profile, selection: $tabSelection)
            
            Color.orange
                .tabBarItem(tab: .messages, selection: $tabSelection)
        }
    }
}

struct AppTabBarView_Previews: PreviewProvider {
    static var previews: some View {
        AppTabBarView()
    }
}

7. 效果图:

相关推荐
I烟雨云渊T6 小时前
iOS 门店营收表格功能的实现
ios
明月看潮生12 小时前
青少年编程与数学 01-011 系统软件简介 07 iOS操作系统
ios·青少年编程·操作系统·系统软件
90后的晨仔14 小时前
RxSwift 框架解析
前端·ios
Humbunklung15 小时前
Rust Floem UI 框架使用简介
开发语言·ui·rust
大熊猫侯佩18 小时前
由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(五)
swiftui·swift·apple watch
可爱小仙子18 小时前
ios苹果系统,js 滑动屏幕、锚定无效
前端·javascript·ios
未来猫咪花19 小时前
# Flutter状态管理对比:view_model vs Riverpod
flutter·ios·android studio
咕噜企业签名分发-淼淼1 天前
开发源码搭建一码双端应用分发平台教程:逐步分析注意事项
android·ios
CodeCraft Studio1 天前
【案例分享】如何借助JS UI组件库DHTMLX Suite构建高效物联网IIoT平台
javascript·物联网·ui
插件开发1 天前
免费插件集-illustrator插件-Ai插件-随机填色
ui·illustrator