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

相关推荐
2501_916007471 小时前
如何在 Windows 电脑上调试 iOS 设备上的 Safari?完整方案与实战经验分享
android·windows·ios·小程序·uni-app·iphone·safari
2501_915918411 小时前
uni-app iOS日志管理实战,从调试控制台到系统日志的全链路采集与分析指南
android·ios·小程序·https·uni-app·iphone·webview
ajassi20001 小时前
开源 Objective-C IOS 应用开发(一)macOS 的使用
macos·ios
ajassi20001 小时前
开源 Objective-C IOS 应用开发(二)Xcode安装
ios·objective-c·xcode
YungFan2 小时前
SwiftUI-WebView 全面指南
ios·swiftui
猪哥帅过吴彦祖2 小时前
Flutter 从入门到精通:深入 Navigator 2.0 - GoRouter 路由完全指南
android·flutter·ios
Kapaseker4 小时前
Kotlin 跨平台开发中的权衡
android·ios·kotlin
CocoaKier5 小时前
苹果上线App Store Web版本,以后浏览外区更方便了
ios·apple
海鸥两三10 小时前
uniapp 小程序引入 uview plus 框架,获得精美的UI框架
前端·vue.js·ui·小程序·uni-app
UI设计兰亭妙微17 小时前
从0到1:兰亭妙微如何用“小程序思维”重构用户体验路径
ui