In SwiftUI, I am trying to place page Indicators on top of a bottom toolbar, but have not come to a resolution.
Paging Indicators
Right now, I have a tabview that organizes Views 1-7 horizontally, but the page indicators are on its own island at the bottom of the screen:
TabView {
View1()
View2()
View3()
View4()
View5()
View6()
View7()
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
I am trying to place the indicators on top of a toolbar with other buttons like how the Apple Weather App has done it:
Apple Weather App
I have also tried using a NavigationView with the .toolbar(ToolbarItemGroup) modifier, but that has not worked for me either.
Please let me know if you can help me with this. Thanks
CodePudding user response:
You can do this by wrapping a UIPageControl
in a UIViewRepresentable
, and then overlay that over your TabView
using a ZStack
or a .overlay
modifier. You'll want to use .tabViewStyle(.page(indexDisplayMode: .never))
to prevent the tab view from displaying its own page control.
Here's a wrapper for UIPageControl
.
struct PageControl: UIViewRepresentable {
@Binding var currentPage: Int
var numberOfPages: Int
func makeCoordinator() -> Coordinator {
return Coordinator(currentPage: $currentPage)
}
func makeUIView(context: Context) -> UIPageControl {
let control = UIPageControl()
control.numberOfPages = 1
control.setIndicatorImage(UIImage(systemName: "location.fill"), forPage: 0)
control.pageIndicatorTintColor = UIColor(.primary)
control.currentPageIndicatorTintColor = UIColor(.accentColor)
control.translatesAutoresizingMaskIntoConstraints = false
control.setContentHuggingPriority(.required, for: .horizontal)
control.addTarget(
context.coordinator,
action: #selector(Coordinator.pageControlDidFire(_:)),
for: .valueChanged)
return control
}
func updateUIView(_ control: UIPageControl, context: Context) {
context.coordinator.currentPage = $currentPage
control.numberOfPages = numberOfPages
control.currentPage = currentPage
}
class Coordinator {
var currentPage: Binding<Int>
init(currentPage: Binding<Int>) {
self.currentPage = currentPage
}
@objc
func pageControlDidFire(_ control: UIPageControl) {
currentPage.wrappedValue = control.currentPage
}
}
}
And here's an example of how to use it:
struct ContentView: View {
@State var page = 0
var locations = ["Current Location", "San Francisco", "Chicago", "New York", "London"]
var body: some View {
ZStack(alignment: .bottom) {
tabView
VStack {
Spacer()
controlBar.padding()
Spacer().frame(height: 60)
}
}
}
@ViewBuilder
private var tabView: some View {
TabView(selection: $page) {
ForEach(locations.indices, id: \.self) { i in
WeatherPage(location: locations[i])
.tag(i)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
@ViewBuilder
private var controlBar: some View {
HStack {
Image(systemName: "map")
Spacer()
PageControl(
currentPage: $page,
numberOfPages: locations.count
)
Spacer()
Image(systemName: "list.bullet")
}
}
}
struct WeatherPage: View {
var location: String
var body: some View {
VStack {
Spacer()
Text("Weather in \(location)")
Spacer()
}
}
}