For simpler usage, ElegantPagesView is recommended as it loads all page views immediately.
For more complex usage, ElegantListView is recommended as it loads page views on demand(learn more).
The elegance of both these views is that they work as a paging component should be intended to work. One bug that is often seen in SwiftUI is that ScrollView, List, or any Gesture almost certainly interferes with other gestures in the view. However, ElegantPages fixes this issue and scrolling through a paging component even with embedded Gestures works elegantly.
import ElegantPages
let listData = (1...40).map { _ in "Ideally, this should be more dynamic content to make the most use out of this list" }
struct ElegantVListExample: View {
let manager = ElegantListManager(pageCount: vListData.count, pageTurnType: .earlyCutOffDefault)
var body: some View {
ElegantVList(manager: manager,
pageTurnType: .earlyCutOffDefault) { page in
ExampleView(page: page).erased
}
}
}
struct ExampleView: View {
let page: Int
var body: some View {
VStack {
Text("Page \(page)")
.font(.largeTitle)
Text(listData[page])
.font(.title)
}
.padding()
}
}
How it works
ElegantPagesView is pretty simple. It uses a function builder to gather the page views and puts them in either a HStack or VStack depending on the type of ElegantPages view chosen. As a result, all views are created immediately.
ElegantListView is quite interesting. For more flexibility, it uses a @ViewBuilder to get the view for any given page(it’s the closure at the end of the ElegantVList declaration. When it is first initialized, it calls this closure at most 3 times, to get the views for the starting pages. These views are used to initialize an array of at most 3 UIHostingControllers, whose rootViews are set to a specific origin in a UIViewController. Here’s the catch, at any given moment, there are at most only 3 pages loaded. As the user scrolls to the next page, old pages are removed and new pages are inserted; the views themselves are juggled as their origins are changed per page turn. This keeps overall memory usage down and also makes scrolling blazingly fast. If you’re curious, take a peek.
Customization
The following aspects of any ElegantPages component can be customized:
pageTurnType: Whether to scroll to the next page early or until the user lets go of the drag
public enum PageTurnType {
case regular(pageTurnDelta: CGFloat)
case earlyCutoff(config: EarlyCutOffConfiguration)
}
public struct EarlyCutOffConfiguration {
public let scrollResistanceCutOff: CGFloat
public let pageTurnCutOff: CGFloat
public let pageTurnAnimation: Animation
}
A regular page turn only turns the page after the user ends their drag.
The pageTurnDelta represents the percentage of how far across the screen the user has to drag in order for the page to turn when they let go. The default value for this is 0.3, as part of an extension of PageTurnType.
The default regular page turn can be accessed through PageTurnType.regularDefault
An early cutoff page turn turns the page when the user drags a certain distance across the screen.
scrollResistanceCutOff: The distance that the view is offset as the user drags.
pageTurnCutOff: The distance across the screen the user has to drag before the page is turned(once this value is reached, the page automatically gets turned to and the user’s ongoing gesture is invalidated).
pageTurnAnimation: The animation used when the page is turned
The default early cut off page turn can be accessed through PageTurnType.earlyCutOffDefault
In case scrollResistanceCutOff isn’t clear, here’s an example. Say we have a horizontally draggable view. If you drag 80 pixels to the right, the offset that is visible to you is also 80 pixels. The amount you scroll is equal to the visible offset. However, if you have a scroll resistance of say 40 pixels, after dragging 80 pixels to the right, you only see that the view has moved 40 pixels to the right. That is why it is called resistance.
$ viewForPage: datasource method called whenever a new page is displayed that asks for the view of the new page. Available only for ElegantList components
// Use as a function
ElegantVList(..., viewForPage: exampleView)
func exampleView(for page: Int) -> AnyView { ExampleView(...) }
// Use as a closure
ElegantHList(...) { page in ExampleView(...) }
onPageChanged: called whenever a new page is shown. Available for all ElegantPages components.
frame: used to set a custom height or width for ElegantList components
// You may want a smaller width for the VList. However, height for the VList will always be the screen height
ElegantVList(...)
.frame(width: ...)
// You may want a smaller height for the HList. However, width for the HList will always be the screen width
ElegantHList(...)
.frame(height: ...)
Demos
The demo shown in the GIF can be checked out on ElegantCalendar.
If you find a bug, or would like to suggest a new feature or enhancement, it’d be nice if you could search the issue tracker first; while we don’t mind duplicates, keeping issues unique helps us save time and considates effort. If you can’t find your issue, feel free to file a new one.
License
This project is licensed under the MIT License - see the LICENSE file for details
ElegantPages
ElegantPages is an efficient and customizable full screen page view written in SwiftUI.
Introduction
ElegantPagescomes with 2 types of components,ElegantPagesViewandElegantListView.For simpler usage,
ElegantPagesViewis recommended as it loads all page views immediately.For more complex usage,
ElegantListViewis recommended as it loads page views on demand(learn more).The elegance of both these views is that they work as a paging component should be intended to work. One bug that is often seen in SwiftUI is that
ScrollView,List, or anyGesturealmost certainly interferes with other gestures in the view. However,ElegantPagesfixes this issue and scrolling through a paging component even with embeddedGesturesworks elegantly.Basic usage
The
ElegantPagesViewcomponent is available throughElegantHPagesandElegantVPages.The
ElegantListViewcomponent is available throughElegantHListandElegantVList.How it works
ElegantPagesViewis pretty simple. It uses a function builder to gather the page views and puts them in either aHStackorVStackdepending on the type ofElegantPagesview chosen. As a result, all views are created immediately.ElegantListViewis quite interesting. For more flexibility, it uses a@ViewBuilderto get the view for any given page(it’s the closure at the end of theElegantVListdeclaration. When it is first initialized, it calls this closure at most 3 times, to get the views for the starting pages. These views are used to initialize an array of at most 3UIHostingControllers, whoserootViewsare set to a specific origin in aUIViewController. Here’s the catch, at any given moment, there are at most only 3 pages loaded. As the user scrolls to the next page, old pages are removed and new pages are inserted; the views themselves are juggled as their origins are changed per page turn. This keeps overall memory usage down and also makes scrolling blazingly fast. If you’re curious, take a peek.Customization
The following aspects of any
ElegantPagescomponent can be customized:pageTurnType: Whether to scroll to the next page early or until the user lets go of the dragA regular page turn only turns the page after the user ends their drag.
pageTurnDeltarepresents the percentage of how far across the screen the user has to drag in order for the page to turn when they let go. The default value for this is 0.3, as part of an extension ofPageTurnType.PageTurnType.regularDefaultAn early cutoff page turn turns the page when the user drags a certain distance across the screen.
scrollResistanceCutOff: The distance that the view is offset as the user drags.pageTurnCutOff: The distance across the screen the user has to drag before the page is turned(once this value is reached, the page automatically gets turned to and the user’s ongoing gesture is invalidated).pageTurnAnimation: The animation used when the page is turnedPageTurnType.earlyCutOffDefaultIn case
scrollResistanceCutOffisn’t clear, here’s an example. Say we have a horizontally draggable view. If you drag 80 pixels to the right, the offset that is visible to you is also 80 pixels. The amount you scroll is equal to the visible offset. However, if you have a scroll resistance of say 40 pixels, after dragging 80 pixels to the right, you only see that the view has moved 40 pixels to the right. That is why it is called resistance.$
viewForPage: datasource method called whenever a new page is displayed that asks for the view of the new page. Available only forElegantListcomponentsonPageChanged: called whenever a new page is shown. Available for allElegantPagescomponents.frame: used to set a custom height or width forElegantListcomponentsDemos
The demo shown in the GIF can be checked out on ElegantCalendar.
For simpler demos, look at the example repo.
Installation
ElegantPagesis available using the Swift Package Manager:Using Xcode 11, go to
File -> Swift Packages -> Add Package Dependencyand enter https://github.com/ThasianX/ElegantPagesIf you are using
Package.swift, you can also addElegantPagesas a dependency easily.Requirements
Contributing
If you find a bug, or would like to suggest a new feature or enhancement, it’d be nice if you could search the issue tracker first; while we don’t mind duplicates, keeping issues unique helps us save time and considates effort. If you can’t find your issue, feel free to file a new one.
License
This project is licensed under the MIT License - see the LICENSE file for details