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. 效果图:

相关推荐
SY.ZHOU10 小时前
大型工程跨全平台实践总结
flutter·ios·安卓·鸿蒙
工控小龙人13 小时前
核电行业HMI:核岛设备的安全监控与操作界面
ui·人机交互·制造·用户界面
denggun1234513 小时前
Sendable 协议-Swift 结构化并发的核心安全保障
ios·swift
奶糖的次元空间14 小时前
iOS 学习笔记 - 创建第一个APP
ios
for_ever_love__14 小时前
Objective-C 学习 NSString 和 NSMutableString的基本功能详解
学习·ios·objective-c
XiaoLeisj16 小时前
Android 文件与数据存储实战:SharedPreferences、SQLite 与 Room 的渐进式实现
android·java·数据库·ui·sqlite·room·sp
@大迁世界16 小时前
注意,苹果刚刚做出改变: iOS 26.4 系统强制所有设备开启“被盗设备保护”功能。
macos·ios·objective-c·cocoa
互联网散修16 小时前
鸿蒙应用开发UI基础第十八节:表单交互核心组件Button、Radio、Toggle示例演示
ui·交互·harmonyos
●VON16 小时前
2G 内存云服务器部署 Spring Boot + MySQL 实战:从踩坑到上线
服务器·开发语言·spring boot·mysql·ui·von
卢锡荣1 天前
LDR6021Q 车规级 Type‑C PD 控制芯片:一芯赋能,边充边传,稳驭全场景
c语言·开发语言·ios·计算机外设·电脑