79784902

Date: 2025-10-07 20:14:02
Score: 1.5
Natty:
Report link

So the limit of 6 tabs is enforced by the UITabBarController() I believe. I could not find a way to amend this limit. A lone instance of a UITabBar() however, will not place any tabs in a more tab, and will allow the developer to break the UI is so desired. My plan is to just implement the UITabBar() , and trust the developer to ensure that each tab has the recommended minimum frame of 44x44 according to the HIG.

My code is based around enums because I find them convenient.

First I created a struct, TabIcon, to collect the icon data:

public struct TabIcon {
  let title : String?
  let icon  : UIImage?

  public init ( title      : String  , systemName: String ) { self.title = title   ;   self.icon = UIImage ( systemName: systemName )  }
  public init ( systemName : String                       ) { self.title = nil     ;   self.icon = UIImage ( systemName: systemName )  }
  public init ( title      : String                       ) { self.title = title   ;   self.icon = nil                                 }
}

Then I implemented the protocol, TabOption. Designed to be placed on enums:

public protocol TabOption: RawRepresentable , CaseIterable , Hashable , View where Self.RawValue == Int {
  static var home: Self { get }
  var tab:  TabIcon { get }
}

( Notice it conforms to View. )
Each case of the enum is potential Tab that can be navigated to.

I ran en extension off of the protocol to extract a UITabBarItem out of each case of the enum.

fileprivate extension TabOption {
   var tabItem: UITabBarItem {
     UITabBarItem ( title: self.tab.title , image: self.tab.icon , tag: self.rawValue )
  }
}

And finally, I created the UIViewRepresentable() responsible for implementing UITabBar() :

public struct CustomTabBar < Case: TabOption >: UIViewRepresentable {
  @Binding var selection: Case
  
  let items: [ UITabBarItem ]
  
  public init ( selection: Binding < Case > ) {
    self._selection = selection
    self.items = Case.allCases.map { $0.tabItem }
  }

  public func makeUIView ( context: Context ) -> UITabBar {
    let tabBar = UITabBar()
    tabBar.items = items
    tabBar.selectedItem = items [ selection.rawValue ]
    tabBar.delegate = context.coordinator
    return tabBar
  }

  public func updateUIView ( _ uiView: UITabBar , context: Context ) { }

  public func makeCoordinator() -> Coordinator { Coordinator ( $selection ) }

  public class Coordinator: NSObject , UITabBarDelegate {
    @Binding var selection: Case
    init ( _ selection: Binding < Case > ) { self._selection = selection }

    public func tabBar ( _ tabBar: UITabBar , didSelect item: UITabBarItem ) {
      selection = Case ( rawValue: item.tag ) ?? .home
    }
  }
}

It binds to a single instance of the protocol, and creates the TabBar() ( which has no limit on tabs. )

For Testing, I created an enum:

public enum Tab: Int , TabOption {
  case home , two , three , four , five , six

  public var tab: TabIcon {
    switch self {
    case .home:  TabIcon ( title: "One"      ,  systemName: "1.circle" )
    case .two:   TabIcon ( title: "Two"      ,  systemName: "2.circle" )
    case .three: TabIcon ( title: "three"    ,  systemName: "3.circle" )
    case .four:  TabIcon ( title: "four"     ,  systemName: "4.circle" )
    case .five:  TabIcon ( title: "settings" ,  systemName: "5.circle" )
    case .six:   TabIcon ( title: "more"     ,  systemName: "6.circle" )
    }
  }
  public var body: some View {
    switch self {
    case .home   : Text ( "one" )
    case .two    : Image ( systemName: "star.fill" ).resizable().frame ( width: 70 , height: 70 )
    case .three  : Circle().fill ( .red )
    case .four   : Circle()
    case .five   : RoundedRectangle ( cornerRadius: 30 ).fill ( .blue ).padding ( 30 )
    case .six    : Rectangle()
    }
  }
}

It conforms to theTabOption protocol, is a view , and has a TabIcon value for each case.

I created a convenience struct that implements the view for the CustomTabView.

fileprivate struct CustomTabView < Case: TabOption > : View {
  @State var selection: Case = .home
  var body: some View {
    VStack ( spacing: 0 ) {
      self.selection .frame ( maxHeight: .infinity , alignment: .center )
      CustomTabBar ( selection: $selection )
    }
    .ignoresSafeArea ( edges: .bottom )
  }
}

And then for ultimate convenience, I implement an extension on the protocol calling the CustomTabView.

public extension TabOption {
  static var tabView: some View { CustomTabView < Self > () }
}

Best Regards:

struct ContentView: View {
  var body: some View {
    Tab.tabView
  }
}
Reasons:
  • Blacklisted phrase (0.5): Best Regards
  • Blacklisted phrase (1): Regards
  • Contains signature (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (0.5):
Posted by: Tim