在SwiftUI中实现自定义的高度灵活的标签栏

在SwiftUI中实现自定义的高度灵活的标签栏

标签栏组件是UI界面中非常重要的组件,它通过创建一组清晰的选项卡按钮(标签),使得用户能够轻松地识别并切换到不同的内容区域,从而在一个界面上展现多个不同主题的丰富内容 。

SwiftUI提供了一个内置的标签栏组件,即TabView组件,但这个组件不够灵活,虽然通过定制可以解决一些问题,但很难适应一些更复杂的场景。

这篇博文介绍一种完全自定义的高度灵活的标签栏实现方式,可以作为SwiftUI 的标签栏组件的补充。

设计思路

通常标签栏组件可以分解为三部分: 标签部分(Tab),标签栏部分(TabBar),以及标签内容部分,即TabPage。我们的思路是,将这三部分独立设计,让它们独立发展,从而可以提供各种组合以实现高度灵活性。 标签部分需要数据支持,可以是数组,也可以是枚举,或者其他类型的数据, 我们用协议实现;标签栏的主要功能是对一组标签进行布局排列,我们用定制的组件实现;标签内容部分主要显示当前标签的内容,这个和具体业务需求相关,无需任何设计,直接在业务代码中实现,但需要遵循一定规范。 如下是我们的设计代码:

//标签协议
protocol HDXTab
{
    associatedtype TAB:View
    @ViewBuilder  
    func makeTab(isSelected:Bool,action:@escaping ()->Void)->TAB
}

//标签栏组件 
struct HDXTabBar<Content:View>:View
{
    var content: ()->Content  
    var body:some View 
    {
        self.content() 
    }
}

上面代码中,大家可能会觉得标签栏组件很奇怪,似乎和标签(Tab)没有任何关联,诀窍在于: 在标签栏的Content闭包中,我们可以直接引用标签的数据: 数组或者枚举。换句话说,这个关联是在闭包中完成的。

其实整个标签栏组件也是可以完全省略的,因为它的工作主要是对各标签进行排列布局,用SwiftUI提供各种内置布局组件就可以实现。之所以要设计标签栏组件,仅仅是需要通过一个组件名称以强化语义。

代码示例

下面按上述思路实现一个简单的普通的标签栏。效果如下图:。

在这里插入图片描述

我们用枚举为该组件建模。

枚举和标签栏组件有一种天然的联系,建议优先考虑用枚举为标签栏组件建模

//用枚举建模
enum  DemoTab:CaseIterable  
{
    case 首页  
    case 地址  
    case 收藏  
    case 我的  
    func getText()->String
    {
        return "\(self)"
    }
}

//对DemoTab进行扩展,使其遵循HDXTab协议:

extension DemoTab:HDXTab
{
     
    @ViewBuilder
    func makeTab(isSelected  : Bool, action: @escaping () -> Void) ->some View 
    {
        Button
        {
            action()
        } label:
        {
                 Text( self.getText())
                    .foregroundColor(isSelected ? .blue : .black)
         }
    }
}

下面是使用DemoTab组件的代码:

struct TabViewDemo:View 
{
    @State var selectedTab = DemoTab.首页 
    var body :some View
    {
        VStack
        { 
            ZStack
            {  
                //TabPage
                // Text(  selectedTab.getText()) 
                switch selectedTab {
                case .首页:  homeView()
                case .地址:  addressView()
                case .收藏:  favoriteView()  
                case .我的:  profileView()
                }
            }.frame(width:200,height:100)
            //TabBar
            HDXTabBar 
            {
                HStack { 
                 Spacer()
                    ForEach (DemoTab.allCases,id:\.self) { 
                       item  in   
                        //Tab
                        item.makeTab(isSelected:selectedTab == item) 
                        {
                            selectedTab = item   
                        }
                        Spacer()
                    }
                }
            }
           
        } 
    }
    
    @ViewBuilder
     func homeView()->some View
     {
       Text("this is home view")            
     }
      
    @ViewBuilder
    func addressView()->some View
    {
        Text("this is address view")            
    }
   
    @ViewBuilder
    func favoriteView()->some View
    {
        Text("this is favorite  view")            
    }
   
    @ViewBuilder
    func profileView()->some View
    {
        Text("this is profile view")            
    }
}

上述代码中,我们在标签栏组件中直接引用DemoTab枚举的各个枚举项,并调用其makeTab()方法构造标签,然后对标签进行布局。
另外,请特别注意标签切换的代码,以及展示标签内容(标签页)的代码,看起来似乎有些多,但由于使用了枚举建模,显得清晰和有条理。 HDXTabBar 这一层可以完全拿掉,如前所叙,仅仅是为了强调语义。

可以尝试对上面标签栏进行一些定制。比如,给每个标签加上图标,动画等等,将标签内容放置在标签栏之上,或者左面,或者右面,亦或者当标签比较多时,使用两行布局标签标签等等,这些需求在我们的实现中都非常容易实现。

小结

本文介绍了一种实现标签栏的方式,主要特点是让标签(Tab),标签栏(TabBar)以及标签内容(TabPage)独立变化,灵活组合,从而具有高度的灵活性。灵活性包括但不限于下面几个方面:

  • 定制的Tab风格
  • 定制的TabBar风格
  • 定制TabBar和TabPage之间的位置关系
  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值