I’m trying to replicate the tab bar in the iPad version of Safari, which looks like this:
(Is there a third party library which does this? I can’t find one)
I’m using the code below. Which results in this:
I guess I need to turn the Views into arrays somehow, and have a button (on the tab, or page) to add and remove tabs. Any idea how to do this?
import SwiftUI
struct TabLabel : View {
var text : String
var imageName : String
var color : Color
var body : some View {
VStack() {
Image(systemName: imageName)
Text(text).font(.caption)
}.foregroundColor(color)
}
}
struct TabButton : View {
@Binding var currentSelection : Int
var selectionIndex : Int
var label : TabLabel
var body : some View {
Button(action: { self.currentSelection = self.selectionIndex }) { label }.opacity(selectionIndex == currentSelection ? 0.5 : 1.0)
}
}
struct CustomTabBarView<SomeView1 : View, SomeView2 : View, SomeView3 : View> : View {
var view1 : SomeView1
var view2 : SomeView2
var view3 : SomeView3
@State var currentSelection : Int = 1
var body : some View {
let label1 = TabLabel(text: "First", imageName: "1.square.fill", color: Color.red)
let label2 = TabLabel(text: "Second", imageName: "2.square.fill", color: Color.purple)
let label3 = TabLabel(text: "Third", imageName: "3.square.fill", color: Color.blue)
let button1 = TabButton(currentSelection: $currentSelection, selectionIndex: 1, label: label1)
let button2 = TabButton(currentSelection: $currentSelection, selectionIndex: 2, label: label2)
let button3 = TabButton(currentSelection: $currentSelection, selectionIndex: 3, label: label3)
return VStack() {
HStack() {
button1
Spacer()
button2
Spacer()
button3
}.padding(.horizontal, 48)
.frame(height: 48.0)
.background(Color(UIColor.systemGroupedBackground))
Spacer()
if currentSelection == 1 {
view1
}
else if currentSelection == 2 {
view2
}
else if currentSelection == 3 {
view3
}
Spacer()
}
}
}
struct ContentView: View {
@State private var showGreeting = true
var body: some View {
let view1 = VStack() {
Text("The First Tab").font(.headline)
Image(systemName: "triangle").resizable().aspectRatio(contentMode: .fit).frame(width: 100)
}
let view2 = Text("Another Tab").font(.headline)
let view3 = Text("The Final Tab").font(.headline)
return CustomTabBarView(view1: view1, view2: view2, view3: view3)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
2
Answers
Like a lot of problems with SwiftUI, this seems overly complex because you’re mixing the concept of state with how that state is drawn on screen.
Removing the visuals for a moment, what you have in terms of data is:
(Note that I call them ‘pages’ here to try and separate them from the visual representation as tabs.)
You also have some actions which will alter that data:
Now, if you think of those small steps as your data model, you can build an object or objects which cleanly encapsulate that.
Then, you can go on to determine how SwiftUI represents that data and how it might include the action triggers. For example:
And so on. Hopefully, you can see that each view in SwiftUI would now have a specific purpose, and should be easier for you to think about.
And you might even decide on a different UI – you could list your pages vertically in a
List
, for example, or in a grid like the iPhone’s Safari page view. But even if you did, your underlying data wouldn’t change.I honestly love @Scott Matthewman’s answer! It inspired me to try an implementation – I included Scotts to do points as remarks 🙂
Model:
Views: