Creating a Pager
is very simple. You just need to pass:
Binding
to the current pageArray
of itemsKeyPath
to an identifierViewBuilder
factory method to create each page
@State var page: Int = 0
var items = Array(0..<10)
var body: some View {
Pager(page: $page,
data: items,
id: \.identifier,
content: { index in
// create a page based on the data passed
Text("Page: \(index)")
})
}
Note: All examples require
import SwiftUIPager
at the top of the source file.
Pager
is easily customizable through a number of view-modifier functions. You can change the orientation, the direction of the scroll, the alignment, the space between items or the page aspect ratio, among others:
By default, Pager
is configured as:
- Horizontal, left to right direction.
- Items have center alignment inside
Pager
and take all the space available - Current page is centered in the scroll
- Only the page is hittable and reacts to swipes
- Finite, that is, it doesn't loop the pages
- Single pagination, one page per swipe.
Note
Pager
has no intrinsic size. This means that its size depends on the available space or the extrinsic size specified withframe
modifier.If you're using the leacy support, you'll need to wrap any reference to
Pager
withif #available(iOS 13, *)
or any other platform and version you may require.
There are two ways to achieve this. You can use preferredItemSize
to let Pager
know which size your items should have. The framework will automatically calculate the itemAspectRatio
and the necessary padding
for you. You can also use itemAspectRatio
to change the look of the page. Pass a value lower than 1 to make the page look like a card:
whereas a value greater than one will make it look like a box:
By default, Pager
will increase/decrease the current page
if this shift is greather than 50% of its relative size. If you wish to make this transition easier or, on the contrary, make it harder to happen, use sensitivity
to modify this relative value. For instance, to make transitions easier:
Pager(...)
.sensitivity(.high)
By default, Pager
will create a horizontal container. Use vertical
to create a vertical pager:
Pager(...)
.vertical()
Pass a direction to horizontal
or vertical
to change the scroll direction. For instance, you can have a horizontal Pager
that scrolls right-to-left:
Pager(...)
.itemSpacing(10)
.alignment(.start)
.horizontal(.rightToLeft)
.itemAspectRatio(0.6)
Pass a position to itemAspectRatio
or preferredItemSize
to specify the alignment along the vertical / horizontal axis for a horizontal / vertical Pager
. Change its position along the horizontal / vertical axis of a horizontal / vertical Pager
by using alignment
:
Pager(...)
.itemSpacing(10)
.horizontal()
.padding(8)
.itemAspectRatio(1.5, alignment: .end) // will move the items to the bottom of the container
.alignment(.center) // will align the first item to the leading of the container
By default, Pager
will reveal the neighbor items completely (100% of their relative size). If you wish to limit this reveal ratio, you can use singlePatination(ratio:sensitivity)
to modify this ratio:
Pager(...)
.singlePagination(0.33, sensitivity: .custom(0.2))
.preferredItemSize(CGSize(width: 300, height: 400))
.itemSpacing(10)
.background(Color.gray.opacity(0.2))
For more information about sensitivity
, check out Pagination sensitivity.
It's possible for Pager
to swipe more than one page at a time. This is especially useful if your page size is small. Use multiplePagination
.
Pager(...)
.preferredItemSize(CGSize(width: 300, height: 300)
.multiplePagination()
Be aware that this modifier will change the loading policy. See Content Loading Policy for more information.
Modifier | Description |
---|---|
allowsDragging |
whether or not dragging is allowed |
disableDragging |
disables dragging |
bounces |
whether or not Pager should bounce |
delaysTouches |
whether or not touches shoulf be delayed. Useful if nested in ScrollView |
pageOffset |
allows manual scroll |
expandPageToEdges |
modifies itemAspectRatio so that the use up all the space available |
For complex pages where a Gesture
might be used, or any other View
that internally holds a Gesture
like Button
or NavigationLink
, you might come across issues when trying to swipe on top of certain areas within the page. For these scenarios, use pagingPriority
to select the option that best suits your purpose. For instance, a page containing a NavigationLink
won't be scrollable over the link area unless pagingPrioriry(.simultaneous)
is added:
var body: some View {
NavigationView {
Pager(page: self.$page2,
data: self.data2,
id: \.self) {
self.pageView($0)
}
.pagingPriority(.simultaneous)
.itemSpacing(10)
.padding(20)
.itemAspectRatio(1.3)
.background(Color.gray.opacity(0.2))
.navigationBarTitle("SwiftUIPager", displayMode: .inline)
}
.navigationViewStyle(StackNavigationViewStyle())
}
func pageView(_ page: Int) -> some View {
ZStack {
Rectangle()
.fill(Color.yellow)
NavigationLink(destination: Text("Page \(page)")) {
Text("Page \(page)")
}
}
.cornerRadius(5)
.shadow(radius: 5)
}
Use interactive
to add a scale animation effect to those pages that are unfocused, that is, those elements whose index is different from pageIndex
:
Pager(...)
.interactive(0.8)
You can also use rotation3D
to add a rotation effect to your pages:
Pager(...)
.itemSpacing(10)
.rotation3D()
Transform your Pager
into an endless sroll by using loopPages
:
Note: You'll need a minimum number of elements to use this modifier based on the page size. If you need more items, use loopPages(repeating:)
to let Pager
know elements should be repeated in batches.
Use pagingAnimation
to customize the transition to the next page once the drag has ended. This is achieve by a block with a DragResult
which contains:
- Current page
- Next page
- Total shift
- Velocity
By default, pagingAnimation
is set to standard
(a.k.a, .easeOut
) for singlePagination
and steep
(custom bezier curve) for multiplePagination
. If you wish to change the animation, you could do it as follows:
Pager(...)
.pagingAnimation({ currentPage, nextPage, totalShift, velocity in
return PagingAnimation.custom(animation: .easeInOut)
})
Use onPageChanged
to react to any change on the page index:
Pager(...)
.onPageChanged({ (newIndex) in
// do something
})
You can also use onDraggingBegan
, onDraggingChanged
and onDragginEnded
to keep track of the dragging.
You can use onPageChanged
to add new items on demand whenever the user is getting to the last page:
@State var page: Int = 0
@State var data = Array(0..<5)
var body: some View {
Pager(...)
.onPageChanged({ page in
guard page == self.data.count - 2 else { return }
guard let last = self.data.last else { return }
let newData = (1...5).map { last + $0 }
withAnimation {
self.data.append(contentsOf: newData)
}
})
}
At the same time, items can be added at the start. Notice you'll need to update the page yourself (as you're inserting new elements) to keep Pager
focused on the right element:
@State var count: Int = -1
@State var page: Int = 3
@State var data = Array(0..<5)
Pager(page: self.$page,
data: self.data,
id: \.self) {
self.pageView($0)
}
.onPageChanged({ page in
guard page == 1 else { return }
let newData = (1...5).map { $0 * self.count }
withAnimation {
self.data1.insert(contentsOf: newData, at: 0)
self.page1 += 5
self.count -= 1
}
})
Pager
recycles views by default and won't have loaded all pages in memory at once. In some scenarios, this might be counterproductive, for example, if you're trying to manually scroll from the first page to the last. For these scenarios, use .contentLoadingPolicy
and choose among the options available.
For more information, please check the sample app. There are included several common use-cases:
- See
InfiniteExampleView
for more info aboutloopPages
and usingonPageChanged
to add items on the fly. - See
ColorsExampleView
for more info about event-drivenPager
. - See
EmbeddedExampleView
for more info about embeddingPager
into aScrollView
. - See
NestedExampleView
for more info about nestingPager
. - See
BizarreExampleView
for more info about other features ofPager
.