SwiftUI 3
In iOS 15 a new TabViewStyle
was introduced: CarouselTabViewStyle
(watchOS only).
Also, we can now set styles more easily:
.tabViewStyle(.page)
SwiftUI 2
There is now a native equivalent of UIPageViewController
in SwiftUI 2 / iOS 14.
To create a paged view, add the .tabViewStyle
modifier to TabView
and pass PageTabViewStyle
.
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
TabView {
FirstView()
SecondView()
ThirdView()
}
.tabViewStyle(PageTabViewStyle())
}
}
}
You can also control how the paging dots are displayed:
// hide paging dots
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
You can find a more detailed explanation in this link:
Vertical variant
TabView {
Group {
FirstView()
SecondView()
ThirdView()
}
.rotationEffect(Angle(degrees: -90))
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.rotationEffect(Angle(degrees: 90))
Custom component
If you're tired of passing tabViewStyle
every time you can create your own PageView
:
Note: TabView selection in iOS 14.0 worked differently and that's why I used two Binding
properties: selectionInternal
and selectionExternal
. As of iOS 14.3 it seems to be working with just one Binding
. However, you can still access the original code from the revision history.
struct PageView<SelectionValue, Content>: View where SelectionValue: Hashable, Content: View {
@Binding private var selection: SelectionValue
private let indexDisplayMode: PageTabViewStyle.IndexDisplayMode
private let indexBackgroundDisplayMode: PageIndexViewStyle.BackgroundDisplayMode
private let content: () -> Content
init(
selection: Binding<SelectionValue>,
indexDisplayMode: PageTabViewStyle.IndexDisplayMode = .automatic,
indexBackgroundDisplayMode: PageIndexViewStyle.BackgroundDisplayMode = .automatic,
@ViewBuilder content: @escaping () -> Content
) {
self._selection = selection
self.indexDisplayMode = indexDisplayMode
self.indexBackgroundDisplayMode = indexBackgroundDisplayMode
self.content = content
}
var body: some View {
TabView(selection: $selection) {
content()
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: indexDisplayMode))
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: indexBackgroundDisplayMode))
}
}
extension PageView where SelectionValue == Int {
init(
indexDisplayMode: PageTabViewStyle.IndexDisplayMode = .automatic,
indexBackgroundDisplayMode: PageIndexViewStyle.BackgroundDisplayMode = .automatic,
@ViewBuilder content: @escaping () -> Content
) {
self._selection = .constant(0)
self.indexDisplayMode = indexDisplayMode
self.indexBackgroundDisplayMode = indexBackgroundDisplayMode
self.content = content
}
}
Now you have a default PageView
:
PageView {
FirstView()
SecondView()
ThirdView()
}
which can be customised:
PageView(indexDisplayMode: .always, indexBackgroundDisplayMode: .always) { ... }
or provided with a selection
:
struct ContentView: View {
@State var selection = 1
var body: some View {
VStack {
Text("Selection: (selection)")
PageView(selection: $selection, indexBackgroundDisplayMode: .always) {
ForEach(0 ..< 3, id: .self) {
Text("Page ($0)")
.tag($0)
}
}
}
}
}