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

相关推荐
Swift社区3 小时前
Excel 列名称转换问题 Swift 解答
开发语言·excel·swift
生产队队长6 小时前
项目练习:element-ui的valid表单验证功能用法
前端·vue.js·ui
crasowas20 小时前
iOS - 超好用的隐私清单修复脚本(持续更新)
ios·app store
ii_best1 天前
ios按键精灵脚本开发:ios悬浮窗命令
ios
~央千澈~1 天前
优雅草央千澈-关于蓝湖如何快速的标注交互原型是如何使用的-如何使用蓝湖设计交互原型和整个软件项目的流程逻辑-实践项目详细说明
ui·交互·蓝湖
Code&Ocean1 天前
iOS从Matter的设备认证证书中获取VID和PID
ios·matter·chip
/**书香门第*/1 天前
Laya ios接入goole广告,开始接入 2
ios
东坡肘子1 天前
肘子的 Swift 周报 #063|异种肾脏移植取得突破
swiftui·swift·apple
军训猫猫头1 天前
20.抽卡只有金,带保底(WPF) C#
ui·c#·wpf
wuningw1 天前
ant-design-ui的Select选择器多选时同时获取label与vaule值
ui·arcgis