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
}
}