├── .codecov.yml
├── .github
├── FUNDING.yml
└── workflows
│ ├── build.yml
│ └── publish.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── Artwork
├── artwork.sketch
└── logo.png
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dangerfile
├── Documentation
├── Pageboy 2.0 Migration Guide.md
└── Pageboy 3 Migration Guide.md
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── Package.swift
├── Pageboy.podspec
├── Pageboy.xcconfig
├── Pageboy.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── README.md
├── Sources
├── .swiftlint.yml
├── Examples.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Example tvOS.xcscheme
├── Pageboy.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ ├── Pageboy iOS.xcscheme
│ │ └── Pageboy tvOS.xcscheme
├── Pageboy
│ ├── AutoScrolling
│ │ ├── PageboyAutoScroller.swift
│ │ └── PageboyViewController+AutoScrolling.swift
│ ├── Info.plist
│ ├── Model
│ │ ├── NavigationDirection.swift
│ │ └── Page.swift
│ ├── Pageboy.h
│ ├── PageboyViewController+Management.swift
│ ├── PageboyViewController+ScrollCalculations.swift
│ ├── PageboyViewController+ScrollDetection.swift
│ ├── PageboyViewController+Updating.swift
│ ├── PageboyViewController.swift
│ ├── PrivacyInfo.xcprivacy
│ ├── Protocols
│ │ ├── PageboyViewControllerDataSource.swift
│ │ └── PageboyViewControllerDelegate.swift
│ ├── Transitioning
│ │ ├── PageboyViewController+Transitioning.swift
│ │ ├── TransitionOperation+Action.swift
│ │ └── TransitionOperation.swift
│ ├── UIViewController+Pageboy.swift
│ └── Utilities
│ │ ├── Extensions
│ │ ├── DispatchQueue+main.swift
│ │ ├── UIApplication+SafeShared.swift
│ │ ├── UIPageViewController+ScrollView.swift
│ │ ├── UIScrollView+Interaction.swift
│ │ ├── UIView+Animation.swift
│ │ ├── UIView+AutoLayout.swift
│ │ └── UIView+Localization.swift
│ │ ├── IndexedObjectMap.swift
│ │ ├── PatchedPageViewController.swift
│ │ └── WeakContainer.swift
├── PageboyTests
│ ├── Info.plist
│ ├── PageboyAutoScrollTests.swift
│ ├── PageboyConfigurationTests.swift
│ ├── PageboyDataSourceTests.swift
│ ├── PageboyInsertionTests.swift
│ ├── PageboyPropertyTests.swift
│ ├── PageboyTests.swift
│ ├── PageboyTransitionTests.swift
│ └── TestComponents
│ │ ├── TestPageChildViewController.swift
│ │ ├── TestPageboyDataSource.swift
│ │ ├── TestPageboyDelegate.swift
│ │ └── TestPageboyViewController.swift
├── Shared
│ ├── GradientBackgroundViewController.swift
│ ├── PageboyStatusView.swift
│ └── UIColor+Pageboy.swift
├── iOS
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon.png
│ │ │ ├── Icon@2x.png
│ │ │ ├── Icon@3x.png
│ │ │ ├── IconPad.png
│ │ │ ├── IconPad@2x.png
│ │ │ └── IconPadPro.png
│ │ ├── Contents.json
│ │ ├── bg_logo_launch.imageset
│ │ │ ├── Contents.json
│ │ │ ├── bg_logo_launch.png
│ │ │ ├── bg_logo_launch@2x.png
│ │ │ └── bg_logo_launch@3x.png
│ │ ├── ic_minus.imageset
│ │ │ ├── Contents.json
│ │ │ └── ic_minus.pdf
│ │ ├── ic_plus.imageset
│ │ │ ├── Contents.json
│ │ │ └── ic_plus.pdf
│ │ └── ic_welcome_icon.imageset
│ │ │ ├── Contents.json
│ │ │ ├── ic_welcome_icon.png
│ │ │ ├── ic_welcome_icon@2x.png
│ │ │ └── ic_welcome_icon@3x.png
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── ChildViewController.swift
│ ├── Example iOS.entitlements
│ ├── Extras
│ │ ├── GradientBackgroundViewController+Appearance.swift
│ │ ├── NavigationController.swift
│ │ ├── Pageboy+NavigationNotifications.swift
│ │ ├── PageboyTouchBar.swift
│ │ ├── ToolbarDelegate.swift
│ │ └── TransparentNavigationBar.swift
│ ├── Info.plist
│ ├── PageViewController.swift
│ └── SceneDelegate.swift
└── tvOS
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── App Icon & Top Shelf Image.brandassets
│ │ ├── App Icon - App Store.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ │ ├── Background.png
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Foreground.png
│ │ │ │ └── Contents.json
│ │ │ └── Middle.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── Middle.png
│ │ │ │ └── Contents.json
│ │ ├── App Icon.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ │ ├── Background.png
│ │ │ │ │ ├── Background@2x.png
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── Foreground.png
│ │ │ │ │ └── Foreground@2x.png
│ │ │ │ └── Contents.json
│ │ │ └── Middle.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Middle.png
│ │ │ │ └── Middle@2x.png
│ │ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── Top Shelf Image Wide.imageset
│ │ │ └── Contents.json
│ │ └── Top Shelf Image.imageset
│ │ │ └── Contents.json
│ └── Contents.json
│ ├── Base.lproj
│ └── LaunchScreen.storyboard
│ ├── ChildViewController.swift
│ ├── Info.plist
│ └── PageViewController.swift
└── fastlane
├── Appfile
└── Fastfile
/.codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 |
3 | ignore:
4 | - "Sources/PageboyTests/**/*"
5 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [msaps]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | push:
4 | branches-ignore:
5 | - 'gh-pages'
6 |
7 | jobs:
8 | Test:
9 | runs-on: macOS-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Prepare
13 | run: |
14 | bundle update --bundler
15 | bundle install
16 | - name: Run tests
17 | env:
18 | SLACK_URL: ${{ secrets.SLACK_URL }}
19 | run: bundle exec fastlane test
20 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 | on:
3 | push:
4 | tags:
5 | - '*'
6 |
7 | jobs:
8 | publish_release:
9 | runs-on: macOS-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Prepare
13 | run: |
14 | bundle update --bundler
15 | bundle install
16 | - name: Publish release
17 | env:
18 | SLACK_URL: ${{ secrets.SLACK_URL }}
19 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
20 | GITHUB_API_TOKEN: ${{ secrets.GH_UIAS_TOKEN }}
21 | run: bundle exec fastlane deploy
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | # Carthage/Checkouts
52 |
53 | Carthage/
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
61 |
62 | fastlane/report.xml
63 | fastlane/Preview.html
64 | fastlane/screenshots
65 | fastlane/test_output
66 | /fastlane/README.md
67 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Artwork/artwork.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Artwork/artwork.sketch
--------------------------------------------------------------------------------
/Artwork/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Artwork/logo.png
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at [uias@sapsford.tech](mailto:uias@sapsford.tech). All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [https://www.contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 | [version]: https://www.contributor-covenant.org/version/1/4/
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Pageboy
2 |
3 | Thanks for your interest in contributing to Pageboy! Please have a read through this document for how you can help! 🎉
4 |
5 | You can help us reach that goal by contributing. Here are some ways you can contribute:
6 |
7 | - [Report any issues or bugs that you find](https://github.com/uias/Pageboy/issues/new)
8 | - [Open issues for any new features you'd like Pageboy to have](https://github.com/uias/Pageboy/issues/new)
9 | - [Implement other tasks selected for development](https://github.com/uias/Pageboy/issues?q=is%3Aissue+is%3Aopen+label%3A%22ready+for+development%22)
10 | - [Help answer questions asked by the community](https://github.com/uias/Pageboy/issues?q=is%3Aopen+is%3Aissue+label%3Aquestion)
11 | - [Spread the word about Pageboy](https://twitter.com/intent/tweet?text=Pageboy,%20UIPageViewController%20done%20properly:%20https://github.com/uias/Pageboy)
12 |
13 | ## Code of conduct
14 |
15 | All contributors are expected to follow our [Code of conduct](CONDUCT.md).
16 | Please read it before making any contributions.
17 |
18 | ## Setting up the project for development
19 |
20 | Nice and simple, clone the repo and then open `Pageboy.xcworkspace` in Xcode.
21 |
22 | The `Pageboy-Example` project is useful for manually testing `Pageboy`, featuring positional debugging labels and visual cues for ensuring everything is running smoothly. 😁
23 |
24 | ## Testing
25 |
26 | ### Running tests
27 |
28 | Tests should be added for all functionality, both when adding new behaviors to existing features, and implementing new ones.
29 |
30 | Pageboy uses `XCTest` to run its tests, which can either be run through Xcode or by running `$ swift test` in the repository.
31 |
32 | ## Architectural overview
33 |
34 | Here is a quick overview of the architecture of Pageboy, to help you orient yourself in the project.
35 |
36 | ### PageboyViewController
37 |
38 | This is the core class of the project, and is the main externally facing component. The class is split up into various extensions for feature segregation to make the project easier to navigate.
39 |
40 | ### Management
41 |
42 | All inner view controller management is contained within the [PageboyViewController+Management](https://github.com/uias/Pageboy/blob/main/Sources/Pageboy/PageboyViewController%2BManagement.swift) extension.
43 |
44 | This is also where the internal `UIPageViewController` instance is managed. Any additional functionality relevant to the `UIPageViewController` or management of child view controllers should be added to this extension.
45 |
46 | ### Scroll Detection
47 |
48 | The [PageboyViewController+ScrollDetection](https://github.com/uias/Pageboy/blob/main/Sources/Pageboy/PageboyViewController%2BScrollDetection.swift) extension handles responding to scroll updates in addition to all the functions for observing the internal scroll view.
49 |
50 | This extension also responds to the internal `UIPageViewControllerDelegate` and handles infinite scrolling behaviour etc.
51 |
52 | ### Transitioning
53 |
54 | The custom transitioning support available in `Pageboy` is provided by the [PageboyViewController+Transitioning](https://github.com/uias/Pageboy/blob/main/Sources/Pageboy/Transitioning/PageboyViewController%2BTransitioning.swift) extension. In conjunction with [TransitionOperation](https://github.com/uias/Pageboy/blob/main/Sources/Pageboy/Transitioning/TransitionOperation.swift) object, custom transitioning is made available through the use of `CATransition` in place of the built in `UIPageViewController` animations.
55 |
56 | Any updates or tweaks to animated transitioning should be made here.
57 |
58 | ## Questions or discussions
59 |
60 | If you have a question about the inner workings of Pageboy, or if you want to discuss a new feature - feel free to [open an issue](https://github.com/uias/Pageboy/issues/new).
61 |
62 | Happy contributing! 👨🏻💻
63 |
--------------------------------------------------------------------------------
/Dangerfile:
--------------------------------------------------------------------------------
1 |
2 | has_source_changes = !git.modified_files.grep(/Sources.*\.swift/).empty? || !git.added_files.grep(/Sources.*\.swift/).empty?
3 | has_tests_changes = !git.modified_files.grep(/Sources\/PageboyTests.*\.swift/).empty? || !git.added_files.grep(/Sources\/PageboyTests.*\.swift/).empty?
4 |
5 | # Make it more obvious that a PR is a work in progress and shouldn't be merged yet
6 | warn("PR is classed as Work in Progress") if github.pr_title.include? "[WIP]"
7 |
8 | # Warn when there is a big PR
9 | warn("This PR might be a little too big, consider breaking it up.") if git.lines_of_code > 500
10 |
11 | # Require PR description
12 | warn "Please provide a summary in the PR description, it makes it easier to understand!" if github.pr_body.length < 5
13 |
14 | # Check for source changes and prompt for test updates if non added.
15 | if (has_source_changes && ! has_tests_changes)
16 | warn("Looks like you changed some source files, should there have been some tests added?")
17 | end
18 |
19 | swiftlint.config_file = 'Sources/.swiftlint.yml'
20 | swiftlint.lint_files
--------------------------------------------------------------------------------
/Documentation/Pageboy 2.0 Migration Guide.md:
--------------------------------------------------------------------------------
1 | # Pageboy 2.0 Migration Guide
2 |
3 | Pageboy 2.0 is the latest major release of Pageboy; a simple, highly informative page view controller for iOS. Pageboy 2.0 introduces several API-breaking changes that should be made aware of.
4 |
5 | This guide aims to provide an easy transition from existing implementations of Pageboy 1.x to the newest API's available in Pageboy 2.0.
6 |
7 | ## Requirements
8 |
9 | - iOS 8.0+
10 | - Xcode 9.0+
11 | - Swift 4.0+
12 |
13 | For anyone wanting to use Pageboy with a Swift 3.x project, please use the latest 1.x release.
14 |
15 | ## What's new
16 |
17 | - **Full Swift 4 Compatibility**
18 | - **iOS 11 Compatibility**
19 | - **Redesigned Data Source:** `PageboyViewControllerDataSource` has been completely redesigned to promote easier reuse and configuration of view controllers.
20 | - **Refactored Delegate:** `PageboyViewControllerDelegate` functions have been refactored to support the latest design guidelines and the redesigned data source.
21 |
22 | ## API Changes
23 |
24 | There are significant changes to the data source and delegates associated with `PageboyViewController` in Pageboy 2.0.
25 |
26 | ### Data Source Changes
27 | `PageboyViewControllerDataSource` has been significantly changed to provide better support for reuse and performance when using large amounts of pages. The basic gist is that rather than returning a static array of view controllers, the data source can now be provided with dynamic view controllers per page index.
28 |
29 | ```swift
30 | // Pageboy 1.x
31 | func viewControllers(forPageboyViewController pageboyViewController: PageboyViewController) -> [UIViewController]? {
32 | return [viewController1, viewController2]
33 | }
34 |
35 | // Pageboy 2.x
36 | func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> PageboyViewController.PageIndex {
37 | return 2
38 | }
39 |
40 | func viewController(for pageboyViewController: PageboyViewController,
41 | at index: PageboyViewController.PageIndex) -> UIViewController? {
42 | return self.viewControllers[index]
43 | }
44 | ```
45 |
46 | The syntax for other data source methods has also been updated to conform to the latest Swift standards.
47 |
48 | ```swift
49 | // Pageboy 1.x
50 | func defaultPageIndex(forPageboyViewController pageboyViewController: PageboyViewController) -> PageboyViewController.PageIndex? {
51 | return nil
52 | }
53 |
54 | // Pageboy 2.x
55 | func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? {
56 | return nil
57 | }
58 | ```
59 |
60 | ### Delegate Changes
61 | `PageboyViewControllerDelegate` has also been significantly modified to support the new data source functions and an updated syntax style.
62 |
63 | #### willScrollToPage
64 | ```swift
65 | // Pageboy 1.x
66 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
67 | willScrollToPageAtIndex index: Int,
68 | direction: PageboyViewController.NavigationDirection,
69 | animated: Bool)
70 |
71 | // Pageboy 2.x
72 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
73 | willScrollToPageAt index: PageboyViewController.PageIndex,
74 | direction: PageboyViewController.NavigationDirection,
75 | animated: Bool)
76 | ```
77 |
78 | #### didScrollToPosition
79 | ```swift
80 | // Pageboy 1.x
81 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
82 | didScrollToPosition position: CGPoint,
83 | direction: PageboyViewController.NavigationDirection)
84 |
85 | // Pageboy 2.x
86 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
87 | didScrollTo position: CGPoint,
88 | direction: PageboyViewController.NavigationDirection,
89 | animated: Bool)
90 | ```
91 |
92 | #### didScrollToPage
93 | ```swift
94 | // Pageboy 1.x
95 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
96 | didScrollToPageAtIndex index: Int,
97 | direction: PageboyViewController.NavigationDirection,
98 | animated: Bool)
99 |
100 | // Pageboy 2.x
101 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
102 | didScrollToPageAt index: PageboyViewController.PageIndex,
103 | direction: PageboyViewController.NavigationDirection,
104 | animated: Bool)
105 | ```
106 |
107 | #### didReload
108 | ```swift
109 | // Pageboy 1.x
110 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
111 | didReload viewControllers: [UIViewController],
112 | currentIndex: PageboyViewController.PageIndex)
113 |
114 | // Pageboy 2.x
115 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
116 | didReloadWith currentViewController: UIViewController,
117 | currentPageIndex: PageboyViewController.PageIndex)
118 | ```
119 |
120 | ### Type Changes
121 |
122 | - `PageboyViewController.PageIndex` is now `PageboyViewController.Page`.
123 | - `PageboyViewController.PageIndex` refers to an `Int` `typealias` for describing raw page index values.
124 |
125 | ### Property Updates
126 | - `pageCount` has been added to `PageboyViewController`.
127 | - `viewControllers` has been removed from `PageboyViewController`.
--------------------------------------------------------------------------------
/Documentation/Pageboy 3 Migration Guide.md:
--------------------------------------------------------------------------------
1 | # Pageboy 3 Migration Guide
2 |
3 | Pageboy 3 is the latest major release of Pageboy; a simple, highly informative page view controller for iOS. Pageboy 3 introduces several API-breaking changes that should be made aware of.
4 |
5 | This guide aims to provide an easy transition from existing implementations of Pageboy 2.x to the newest API's available in Pageboy 3.
6 |
7 | ## Requirements
8 |
9 | - iOS 9.0+
10 | - Xcode 10.0+
11 | - Swift 4.0+
12 |
13 | ## What's new
14 |
15 | - View Controllers can now be inserted and removed dynamically.
16 | - Fixed numerous performance and memory issues.
17 | - Improved Swift 4 & 4.2 compatibility.
18 |
19 | ## API Changes
20 |
21 | ### PageboyViewControllerDelegate
22 | - Default implementations of `PageboyViewControllerDelegate` have been removed - effectively requiring all functions to be implemented.
23 |
24 | ### Properties
25 | - `showsPageControl` has now been removed completely due to [#128](https://github.com/uias/Pageboy/issues/128).
26 |
27 | ### Functions
28 | - `reloadPages()` is now `reloadData()`.
29 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 |
2 | source 'https://rubygems.org'
3 |
4 | gem 'fastlane'
5 | gem 'cocoapods'
6 | gem 'danger'
7 | gem 'danger-swiftlint'
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.7)
5 | base64
6 | nkf
7 | rexml
8 | activesupport (7.2.1)
9 | base64
10 | bigdecimal
11 | concurrent-ruby (~> 1.0, >= 1.3.1)
12 | connection_pool (>= 2.2.5)
13 | drb
14 | i18n (>= 1.6, < 2)
15 | logger (>= 1.4.2)
16 | minitest (>= 5.1)
17 | securerandom (>= 0.3)
18 | tzinfo (~> 2.0, >= 2.0.5)
19 | addressable (2.8.7)
20 | public_suffix (>= 2.0.2, < 7.0)
21 | algoliasearch (1.27.5)
22 | httpclient (~> 2.8, >= 2.8.3)
23 | json (>= 1.5.1)
24 | artifactory (3.0.17)
25 | atomos (0.1.3)
26 | aws-eventstream (1.3.0)
27 | aws-partitions (1.972.0)
28 | aws-sdk-core (3.203.0)
29 | aws-eventstream (~> 1, >= 1.3.0)
30 | aws-partitions (~> 1, >= 1.651.0)
31 | aws-sigv4 (~> 1.9)
32 | jmespath (~> 1, >= 1.6.1)
33 | aws-sdk-kms (1.89.0)
34 | aws-sdk-core (~> 3, >= 3.203.0)
35 | aws-sigv4 (~> 1.5)
36 | aws-sdk-s3 (1.160.0)
37 | aws-sdk-core (~> 3, >= 3.203.0)
38 | aws-sdk-kms (~> 1)
39 | aws-sigv4 (~> 1.5)
40 | aws-sigv4 (1.9.1)
41 | aws-eventstream (~> 1, >= 1.0.2)
42 | babosa (1.0.4)
43 | base64 (0.2.0)
44 | bigdecimal (3.1.8)
45 | claide (1.1.0)
46 | claide-plugins (0.9.2)
47 | cork
48 | nap
49 | open4 (~> 1.3)
50 | cocoapods (1.15.2)
51 | addressable (~> 2.8)
52 | claide (>= 1.0.2, < 2.0)
53 | cocoapods-core (= 1.15.2)
54 | cocoapods-deintegrate (>= 1.0.3, < 2.0)
55 | cocoapods-downloader (>= 2.1, < 3.0)
56 | cocoapods-plugins (>= 1.0.0, < 2.0)
57 | cocoapods-search (>= 1.0.0, < 2.0)
58 | cocoapods-trunk (>= 1.6.0, < 2.0)
59 | cocoapods-try (>= 1.1.0, < 2.0)
60 | colored2 (~> 3.1)
61 | escape (~> 0.0.4)
62 | fourflusher (>= 2.3.0, < 3.0)
63 | gh_inspector (~> 1.0)
64 | molinillo (~> 0.8.0)
65 | nap (~> 1.0)
66 | ruby-macho (>= 2.3.0, < 3.0)
67 | xcodeproj (>= 1.23.0, < 2.0)
68 | cocoapods-core (1.15.2)
69 | activesupport (>= 5.0, < 8)
70 | addressable (~> 2.8)
71 | algoliasearch (~> 1.0)
72 | concurrent-ruby (~> 1.1)
73 | fuzzy_match (~> 2.0.4)
74 | nap (~> 1.0)
75 | netrc (~> 0.11)
76 | public_suffix (~> 4.0)
77 | typhoeus (~> 1.0)
78 | cocoapods-deintegrate (1.0.5)
79 | cocoapods-downloader (2.1)
80 | cocoapods-plugins (1.0.0)
81 | nap
82 | cocoapods-search (1.0.1)
83 | cocoapods-trunk (1.6.0)
84 | nap (>= 0.8, < 2.0)
85 | netrc (~> 0.11)
86 | cocoapods-try (1.2.0)
87 | colored (1.2)
88 | colored2 (3.1.2)
89 | commander (4.6.0)
90 | highline (~> 2.0.0)
91 | concurrent-ruby (1.3.4)
92 | connection_pool (2.4.1)
93 | cork (0.3.0)
94 | colored2 (~> 3.1)
95 | danger (9.5.0)
96 | claide (~> 1.0)
97 | claide-plugins (>= 0.9.2)
98 | colored2 (~> 3.1)
99 | cork (~> 0.1)
100 | faraday (>= 0.9.0, < 3.0)
101 | faraday-http-cache (~> 2.0)
102 | git (~> 1.13)
103 | kramdown (~> 2.3)
104 | kramdown-parser-gfm (~> 1.0)
105 | octokit (>= 4.0)
106 | terminal-table (>= 1, < 4)
107 | danger-swiftlint (0.36.1)
108 | danger
109 | rake (> 10)
110 | thor (~> 1.0.0)
111 | declarative (0.0.20)
112 | digest-crc (0.6.5)
113 | rake (>= 12.0.0, < 14.0.0)
114 | domain_name (0.6.20240107)
115 | dotenv (2.8.1)
116 | drb (2.2.1)
117 | emoji_regex (3.2.3)
118 | escape (0.0.4)
119 | ethon (0.16.0)
120 | ffi (>= 1.15.0)
121 | excon (0.111.0)
122 | faraday (1.10.3)
123 | faraday-em_http (~> 1.0)
124 | faraday-em_synchrony (~> 1.0)
125 | faraday-excon (~> 1.1)
126 | faraday-httpclient (~> 1.0)
127 | faraday-multipart (~> 1.0)
128 | faraday-net_http (~> 1.0)
129 | faraday-net_http_persistent (~> 1.0)
130 | faraday-patron (~> 1.0)
131 | faraday-rack (~> 1.0)
132 | faraday-retry (~> 1.0)
133 | ruby2_keywords (>= 0.0.4)
134 | faraday-cookie_jar (0.0.7)
135 | faraday (>= 0.8.0)
136 | http-cookie (~> 1.0.0)
137 | faraday-em_http (1.0.0)
138 | faraday-em_synchrony (1.0.0)
139 | faraday-excon (1.1.0)
140 | faraday-http-cache (2.5.1)
141 | faraday (>= 0.8)
142 | faraday-httpclient (1.0.1)
143 | faraday-multipart (1.0.4)
144 | multipart-post (~> 2)
145 | faraday-net_http (1.0.2)
146 | faraday-net_http_persistent (1.2.0)
147 | faraday-patron (1.0.0)
148 | faraday-rack (1.0.0)
149 | faraday-retry (1.0.3)
150 | faraday_middleware (1.2.0)
151 | faraday (~> 1.0)
152 | fastimage (2.3.1)
153 | fastlane (2.222.0)
154 | CFPropertyList (>= 2.3, < 4.0.0)
155 | addressable (>= 2.8, < 3.0.0)
156 | artifactory (~> 3.0)
157 | aws-sdk-s3 (~> 1.0)
158 | babosa (>= 1.0.3, < 2.0.0)
159 | bundler (>= 1.12.0, < 3.0.0)
160 | colored (~> 1.2)
161 | commander (~> 4.6)
162 | dotenv (>= 2.1.1, < 3.0.0)
163 | emoji_regex (>= 0.1, < 4.0)
164 | excon (>= 0.71.0, < 1.0.0)
165 | faraday (~> 1.0)
166 | faraday-cookie_jar (~> 0.0.6)
167 | faraday_middleware (~> 1.0)
168 | fastimage (>= 2.1.0, < 3.0.0)
169 | gh_inspector (>= 1.1.2, < 2.0.0)
170 | google-apis-androidpublisher_v3 (~> 0.3)
171 | google-apis-playcustomapp_v1 (~> 0.1)
172 | google-cloud-env (>= 1.6.0, < 2.0.0)
173 | google-cloud-storage (~> 1.31)
174 | highline (~> 2.0)
175 | http-cookie (~> 1.0.5)
176 | json (< 3.0.0)
177 | jwt (>= 2.1.0, < 3)
178 | mini_magick (>= 4.9.4, < 5.0.0)
179 | multipart-post (>= 2.0.0, < 3.0.0)
180 | naturally (~> 2.2)
181 | optparse (>= 0.1.1, < 1.0.0)
182 | plist (>= 3.1.0, < 4.0.0)
183 | rubyzip (>= 2.0.0, < 3.0.0)
184 | security (= 0.1.5)
185 | simctl (~> 1.6.3)
186 | terminal-notifier (>= 2.0.0, < 3.0.0)
187 | terminal-table (~> 3)
188 | tty-screen (>= 0.6.3, < 1.0.0)
189 | tty-spinner (>= 0.8.0, < 1.0.0)
190 | word_wrap (~> 1.0.0)
191 | xcodeproj (>= 1.13.0, < 2.0.0)
192 | xcpretty (~> 0.3.0)
193 | xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
194 | ffi (1.17.0-arm64-darwin)
195 | ffi (1.17.0-x86_64-darwin)
196 | fourflusher (2.3.1)
197 | fuzzy_match (2.0.4)
198 | gh_inspector (1.1.3)
199 | git (1.19.1)
200 | addressable (~> 2.8)
201 | rchardet (~> 1.8)
202 | google-apis-androidpublisher_v3 (0.54.0)
203 | google-apis-core (>= 0.11.0, < 2.a)
204 | google-apis-core (0.11.3)
205 | addressable (~> 2.5, >= 2.5.1)
206 | googleauth (>= 0.16.2, < 2.a)
207 | httpclient (>= 2.8.1, < 3.a)
208 | mini_mime (~> 1.0)
209 | representable (~> 3.0)
210 | retriable (>= 2.0, < 4.a)
211 | rexml
212 | google-apis-iamcredentials_v1 (0.17.0)
213 | google-apis-core (>= 0.11.0, < 2.a)
214 | google-apis-playcustomapp_v1 (0.13.0)
215 | google-apis-core (>= 0.11.0, < 2.a)
216 | google-apis-storage_v1 (0.31.0)
217 | google-apis-core (>= 0.11.0, < 2.a)
218 | google-cloud-core (1.7.1)
219 | google-cloud-env (>= 1.0, < 3.a)
220 | google-cloud-errors (~> 1.0)
221 | google-cloud-env (1.6.0)
222 | faraday (>= 0.17.3, < 3.0)
223 | google-cloud-errors (1.4.0)
224 | google-cloud-storage (1.47.0)
225 | addressable (~> 2.8)
226 | digest-crc (~> 0.4)
227 | google-apis-iamcredentials_v1 (~> 0.1)
228 | google-apis-storage_v1 (~> 0.31.0)
229 | google-cloud-core (~> 1.6)
230 | googleauth (>= 0.16.2, < 2.a)
231 | mini_mime (~> 1.0)
232 | googleauth (1.8.1)
233 | faraday (>= 0.17.3, < 3.a)
234 | jwt (>= 1.4, < 3.0)
235 | multi_json (~> 1.11)
236 | os (>= 0.9, < 2.0)
237 | signet (>= 0.16, < 2.a)
238 | highline (2.0.3)
239 | http-cookie (1.0.7)
240 | domain_name (~> 0.5)
241 | httpclient (2.8.3)
242 | i18n (1.14.5)
243 | concurrent-ruby (~> 1.0)
244 | jmespath (1.6.2)
245 | json (2.7.2)
246 | jwt (2.8.2)
247 | base64
248 | kramdown (2.4.0)
249 | rexml
250 | kramdown-parser-gfm (1.1.0)
251 | kramdown (~> 2.0)
252 | logger (1.6.1)
253 | mini_magick (4.13.2)
254 | mini_mime (1.1.5)
255 | minitest (5.25.1)
256 | molinillo (0.8.0)
257 | multi_json (1.15.0)
258 | multipart-post (2.4.1)
259 | nanaimo (0.3.0)
260 | nap (1.1.0)
261 | naturally (2.2.1)
262 | netrc (0.11.0)
263 | nkf (0.2.0)
264 | octokit (9.1.0)
265 | faraday (>= 1, < 3)
266 | sawyer (~> 0.9)
267 | open4 (1.3.4)
268 | optparse (0.5.0)
269 | os (1.1.4)
270 | plist (3.7.1)
271 | public_suffix (4.0.7)
272 | rake (13.2.1)
273 | rchardet (1.8.0)
274 | representable (3.2.0)
275 | declarative (< 0.1.0)
276 | trailblazer-option (>= 0.1.1, < 0.2.0)
277 | uber (< 0.2.0)
278 | retriable (3.1.2)
279 | rexml (3.3.7)
280 | rouge (2.0.7)
281 | ruby-macho (2.5.1)
282 | ruby2_keywords (0.0.5)
283 | rubyzip (2.3.2)
284 | sawyer (0.9.2)
285 | addressable (>= 2.3.5)
286 | faraday (>= 0.17.3, < 3)
287 | securerandom (0.3.1)
288 | security (0.1.5)
289 | signet (0.19.0)
290 | addressable (~> 2.8)
291 | faraday (>= 0.17.5, < 3.a)
292 | jwt (>= 1.5, < 3.0)
293 | multi_json (~> 1.10)
294 | simctl (1.6.10)
295 | CFPropertyList
296 | naturally
297 | terminal-notifier (2.0.0)
298 | terminal-table (3.0.2)
299 | unicode-display_width (>= 1.1.1, < 3)
300 | thor (1.0.1)
301 | trailblazer-option (0.1.2)
302 | tty-cursor (0.7.1)
303 | tty-screen (0.8.2)
304 | tty-spinner (0.9.3)
305 | tty-cursor (~> 0.7)
306 | typhoeus (1.4.1)
307 | ethon (>= 0.9.0)
308 | tzinfo (2.0.6)
309 | concurrent-ruby (~> 1.0)
310 | uber (0.1.0)
311 | unicode-display_width (2.5.0)
312 | word_wrap (1.0.0)
313 | xcodeproj (1.25.0)
314 | CFPropertyList (>= 2.3.3, < 4.0)
315 | atomos (~> 0.1.3)
316 | claide (>= 1.0.2, < 2.0)
317 | colored2 (~> 3.1)
318 | nanaimo (~> 0.3.0)
319 | rexml (>= 3.3.2, < 4.0)
320 | xcpretty (0.3.0)
321 | rouge (~> 2.0.7)
322 | xcpretty-travis-formatter (1.0.1)
323 | xcpretty (~> 0.2, >= 0.0.7)
324 |
325 | PLATFORMS
326 | arm64-darwin-22
327 | x86_64-darwin-21
328 |
329 | DEPENDENCIES
330 | cocoapods
331 | danger
332 | danger-swiftlint
333 | fastlane
334 |
335 | BUNDLED WITH
336 | 2.4.19
337 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 UI At Six
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.7
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Pageboy",
8 | platforms: [
9 | .iOS(.v12),
10 | .tvOS(.v12)
11 | ],
12 | products: [
13 | .library(
14 | name: "Pageboy",
15 | targets: ["Pageboy"])
16 | ],
17 | targets: [
18 | .target(
19 | name: "Pageboy",
20 | path: "Sources/Pageboy",
21 | exclude: ["Pageboy.h", "PrivacyInfo.xcprivacy"],
22 | resources: [.process("PrivacyInfo.xcprivacy")]
23 | ),
24 | .testTarget(
25 | name: "PageboyTests",
26 | dependencies: ["Pageboy"]
27 | )
28 | ],
29 | swiftLanguageVersions: [.v5]
30 | )
31 |
--------------------------------------------------------------------------------
/Pageboy.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 |
3 | s.name = "Pageboy"
4 |
5 | s.ios.deployment_target = '12.0'
6 | s.tvos.deployment_target = '12.0'
7 |
8 | s.swift_versions = ['5.0']
9 |
10 | s.requires_arc = true
11 |
12 | s.version = "4.2.0"
13 | s.summary = "A simple, highly informative page view controller."
14 | s.description = <<-DESC
15 | A page view controller that provides simplified data source management, enhanced delegation and other useful features.
16 | DESC
17 |
18 | s.homepage = "https://github.com/uias/Pageboy"
19 | s.license = "MIT"
20 | s.author = { "Merrick Sapsford" => "merrick@sapsford.tech" }
21 | s.social_media_url = "https://twitter.com/MerrickSapsford"
22 |
23 | s.source = { :git => "https://github.com/uias/Pageboy.git", :tag => s.version.to_s }
24 | s.source_files = "Sources/Pageboy/**/*.{h,m,swift}"
25 |
26 | s.resource_bundles = {'Pageboy' => ['Sources/Pageboy/PrivacyInfo.xcprivacy']}
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/Pageboy.xcconfig:
--------------------------------------------------------------------------------
1 | PB_VERSION=4.2.0
2 |
3 | PB_IOS_DEPLOYMENT_TARGET=12.0
4 | PB_TVOS_DEPLOYMENT_TARGET=12.0
5 |
--------------------------------------------------------------------------------
/Pageboy.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Pageboy.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | **TL;DR** *UIPageViewController done properly.*
19 |
20 | ## ⭐️ Features
21 | - [x] Simplified data source management & enhanced delegation.
22 | - [x] Dynamically insert & remove pages.
23 | - [x] Infinite scrolling support.
24 | - [x] Automatic timer-based page transitioning.
25 | - [x] Support for custom animated page transitions.
26 |
27 | ## 📋 Requirements
28 | Pageboy requires iOS 12 / tvOS 12; and is compatible with Swift 5.
29 |
30 | ## 📲 Installation
31 |
32 | ### Swift Package Manager
33 | Pageboy is compatible with [Swift Package Manager](https://swift.org/package-manager) and can be integrated via Xcode.
34 |
35 | ### CocoaPods
36 | Pageboy is also available through [CocoaPods](https://cocoapods.org):
37 | ```ruby
38 | pod 'Pageboy', '~> 4.2'
39 | ```
40 |
41 | ### Carthage
42 | Pageboy is also available through [Carthage](https://github.com/Carthage/Carthage):
43 | ```ogdl
44 | github "uias/Pageboy" ~> 4.2
45 | ```
46 |
47 | ## 🚀 Usage
48 | - [The Basics](#the-basics)
49 | - [PageboyViewControllerDelegate](#pageboyViewControllerDelegate)
50 | - [Navigation](#navigation)
51 | - [Insertion & Deletion](#insertion-&-deletion)
52 |
53 | ### The Basics
54 |
55 | 1) Create an instance of a `PageboyViewController` and provide it with a `PageboyViewControllerDataSource`.
56 |
57 | ```swift
58 | class PageViewController: PageboyViewController, PageboyViewControllerDataSource {
59 |
60 | override func viewDidLoad() {
61 | super.viewDidLoad()
62 |
63 | self.dataSource = self
64 | }
65 | }
66 | ```
67 |
68 | 2) Implement the `PageboyViewControllerDataSource` functions.
69 |
70 | ```swift
71 | func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int {
72 | return viewControllers.count
73 | }
74 |
75 | func viewController(for pageboyViewController: PageboyViewController,
76 | at index: PageboyViewController.PageIndex) -> UIViewController? {
77 | return viewControllers[index]
78 | }
79 |
80 | func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? {
81 | return nil
82 | }
83 | ```
84 |
85 | ### PageboyViewControllerDelegate
86 |
87 | The delegate functions provided by a `PageboyViewController` are much more reliable and useful than what a raw `UIPageViewController` provides. You can use them to find out exactly where the current page is, and when it's moved, where it's headed.
88 |
89 | #### willScrollToPageAtIndex
90 | About to embark on a transition to a new page.
91 |
92 | ```swift
93 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
94 | willScrollToPageAt index: Int,
95 | direction: PageboyViewController.NavigationDirection,
96 | animated: Bool)
97 | ```
98 |
99 | #### didScrollToPosition
100 | Scrolled to a relative position along the way transitioning to a new page.
101 |
102 | ```swift
103 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
104 | didScrollTo position: CGPoint,
105 | direction: PageboyViewController.NavigationDirection,
106 | animated: Bool)
107 | ```
108 |
109 | #### didScrollToPage
110 | Successfully completed a scroll transition to a page.
111 |
112 | ```swift
113 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
114 | didScrollToPageAt index: Int,
115 | direction: PageboyViewController.NavigationDirection,
116 | animated: Bool)
117 | ```
118 |
119 | #### didReload
120 | Child view controllers have been reloaded.
121 |
122 | ```swift
123 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
124 | didReloadWith currentViewController: UIViewController,
125 | currentPageIndex: PageIndex)
126 | ```
127 |
128 | ### Navigation
129 | You can navigate programmatically through a `PageboyViewController` using `scrollToPage()`:
130 | ```swift
131 | pageViewController.scrollToPage(.next, animated: true)
132 | ```
133 |
134 | - Infinite scrolling can be enabled with `.isInfiniteScrollEnabled`.
135 | - Interactive scrolling can also be controlled with `.isScrollEnabled`.
136 |
137 | ### Insertion & Deletion
138 | Pageboy provides the ability to insert and delete pages dynamically in the `PageboyViewController`.
139 |
140 | ```swift
141 | func insertPage(at index: PageIndex, then updateBehavior: PageUpdateBehavior)
142 | func deletePage(at index: PageIndex, then updateBehavior: PageUpdateBehavior)
143 | ```
144 |
145 | *This behaves similarly to the insertion of rows in `UITableView`, in the fact that you have to update the data source prior to calling any of the update functions.*
146 |
147 | **Example:**
148 |
149 | ```swift
150 | let index = 2
151 | viewControllers.insert(UIViewController(), at: index)
152 | pageViewController.insertPage(at: index)
153 | ```
154 |
155 | *The default behavior after inserting or deleting a page is to scroll to the update location, this however can be configured by passing a `PageUpdateBehavior` value other than `.scrollToUpdate`.*
156 |
157 | ## ⚡️ Other Extras
158 |
159 | - `reloadData()` - Reload the view controllers in the page view controller. (Reloads the data source).
160 | - `.navigationOrientation` - Whether to orientate the pages horizontally or vertically.
161 | - `.currentViewController` - The currently visible view controller if it exists.
162 | - `.currentPosition` - The exact current relative position of the page view controller.
163 | - `.currentIndex` - The index of the currently visible page.
164 | - `.parentPageboy` - Access the immediate parent `PageboyViewController` from any child view controller.
165 |
166 | ### Animated Transitions
167 | Pageboy also provides custom transition support for **animated transitions**. This can be customized via the `.transition` property on `PageboyViewController`.
168 |
169 | ```swift
170 | pageboyViewController.transition = Transition(style: .push, duration: 1.0)
171 | ```
172 |
173 | *Note: By default this is set to `nil`, which uses the standard animation provided by `UIPageViewController`.*
174 |
175 | ### Auto Scrolling
176 | `PageboyAutoScroller` is available to set up timer based automatic scrolling of the `PageboyViewController`:
177 |
178 | ```swift
179 | pageboyViewController.autoScroller.enable()
180 | ```
181 |
182 | Support for custom intermission duration and other scroll behaviors is also available.
183 |
184 | ## 👨🏻💻 About
185 | - Created by [Merrick Sapsford](https://github.com/msaps) ([@MerrickSapsford](https://twitter.com/MerrickSapsford))
186 | - Contributed to by a growing [list of others](https://github.com/uias/Pageboy/graphs/contributors).
187 |
188 | ## ❤️ Contributing
189 | Bug reports and pull requests are welcome on GitHub at [https://github.com/uias/Pageboy](https://github.com/uias/Pageboy).
190 |
191 | ## 👮🏻♂️ License
192 | The library is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
193 |
--------------------------------------------------------------------------------
/Sources/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - trailing_whitespace
3 | - vertical_whitespace
4 | - line_length
5 | - opening_brace
6 | - large_tuple
7 | - nesting
8 | - function_body_length
9 |
10 | opt_in_rules:
11 | - closure_spacing
12 | - conditional_returns_on_newline
13 | #- empty_count
14 | - explicit_init
15 | - force_unwrapping
16 | #- missing_docs
17 | - overridden_super_call
18 | - private_outlet
19 | - switch_case_on_newline
20 |
21 | excluded:
22 | - Pods
23 | - PageboyTests
24 |
--------------------------------------------------------------------------------
/Sources/Examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Sources/Examples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Sources/Examples.xcodeproj/xcshareddata/xcschemes/Example tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Sources/Pageboy.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Sources/Pageboy.xcodeproj/xcshareddata/xcschemes/Pageboy iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
69 |
70 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
88 |
90 |
91 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/Sources/Pageboy.xcodeproj/xcshareddata/xcschemes/Pageboy tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/Sources/Pageboy/AutoScrolling/PageboyAutoScroller.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyAutoScroller.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 08/03/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Internal protocol for handling auto scroller events.
12 | internal protocol PageboyAutoScrollerHandler: AnyObject {
13 |
14 | /// Auto scroller requires a scroll.
15 | ///
16 | /// - Parameter autoScroller: The auto scroller.
17 | /// - Parameter animated: Whether the scroll should be animated.
18 | func autoScroller(didRequestAutoScroll autoScroller: PageboyAutoScroller, animated: Bool)
19 | }
20 |
21 | /// Delegate protocol for observing auto scroll events.
22 | public protocol PageboyAutoScrollerDelegate: AnyObject {
23 |
24 | /// The auto scroller will begin a scroll animation on the page view controller.
25 | ///
26 | /// - Parameter autoScroller: The auto scroller.
27 | func autoScroller(willBeginScrollAnimation autoScroller: PageboyAutoScroller)
28 |
29 | /// The auto scroller did finish a scroll animation on the page view controller.
30 | ///
31 | /// - Parameter autoScroller: The auto scroller.
32 | func autoScroller(didFinishScrollAnimation autoScroller: PageboyAutoScroller)
33 | }
34 |
35 | /// Object that provides auto scrolling framework to PageboyViewController
36 | public class PageboyAutoScroller: Any {
37 |
38 | // MARK: Types
39 |
40 | /// Duration spent on each page.
41 | ///
42 | /// - short: Short (5 seconds)
43 | /// - long: Long (10 seconds)
44 | /// - custom: Custom duration
45 | public enum IntermissionDuration {
46 | case short
47 | case long
48 | case custom(duration: TimeInterval)
49 | }
50 |
51 | // MARK: Properties
52 |
53 | /// The timer
54 | fileprivate var timer: Timer?
55 |
56 | /// Whether the auto scroller is enabled.
57 | public private(set) var isEnabled: Bool = false
58 | /// Whether the auto scroller was previously cancelled
59 | internal var wasCancelled: Bool?
60 | /// Whether a scroll animation is currently active.
61 | internal fileprivate(set) var isScrolling: Bool?
62 |
63 | /// The object that acts as a handler for auto scroll events.
64 | internal weak var handler: PageboyAutoScrollerHandler?
65 |
66 | /// The duration spent on each page during auto scrolling. Default: .short
67 | public private(set) var intermissionDuration: IntermissionDuration = .short
68 | /// Whether auto scrolling is disabled on drag of the page view controller.
69 | public var cancelsOnScroll: Bool = true
70 | /// Whether auto scrolling restarts when a page view controller scroll ends.
71 | public var restartsOnScrollEnd: Bool = false
72 | /// Whether the auto scrolling transitions should be animated.
73 | public var animateScroll: Bool = true
74 |
75 | /// The object that acts as a delegate to the auto scroller.
76 | public weak var delegate: PageboyAutoScrollerDelegate?
77 |
78 | // MARK: State
79 |
80 | /// Enable auto scrolling behaviour.
81 | ///
82 | /// - Parameter duration: The duration that should be spent on each page.
83 | public func enable(withIntermissionDuration duration: IntermissionDuration? = nil) {
84 | guard !isEnabled else {
85 | return
86 | }
87 |
88 | if let duration = duration {
89 | intermissionDuration = duration
90 | }
91 |
92 | isEnabled = true
93 | createTimer(withDuration: intermissionDuration.rawValue)
94 | }
95 |
96 | /// Disable auto scrolling behaviour
97 | public func disable() {
98 | guard isEnabled else {
99 | return
100 | }
101 |
102 | destroyTimer()
103 | isEnabled = false
104 | }
105 |
106 | /// Cancel the current auto scrolling behaviour.
107 | internal func cancel() {
108 | guard isEnabled else {
109 | return
110 | }
111 | wasCancelled = true
112 | disable()
113 | }
114 |
115 | /// Restart auto scrolling behaviour if it was previously cancelled.
116 | internal func restart() {
117 | guard wasCancelled == true && !isEnabled else {
118 | return
119 | }
120 |
121 | wasCancelled = nil
122 | enable()
123 | }
124 |
125 | /// Pause auto scrolling temporarily
126 | internal func pause() {
127 | guard isEnabled else {
128 | return
129 | }
130 | destroyTimer()
131 | }
132 |
133 | /// Resume auto scrolling if it was previously paused
134 | internal func resume() {
135 | guard isEnabled && timer == nil else {
136 | return
137 | }
138 | createTimer(withDuration: intermissionDuration.rawValue)
139 | }
140 | }
141 |
142 | // MARK: - Intervals
143 | internal extension PageboyAutoScroller.IntermissionDuration {
144 | var rawValue: TimeInterval {
145 | switch self {
146 | case .short:
147 | return 5.0
148 | case .long:
149 | return 10.0
150 | case .custom(let duration):
151 | return duration
152 | }
153 | }
154 | }
155 |
156 | // MARK: - Timer
157 | internal extension PageboyAutoScroller {
158 |
159 | /// Initialize auto scrolling timer
160 | ///
161 | /// - Parameter duration: The duration for the timer.
162 | func createTimer(withDuration duration: TimeInterval) {
163 | guard timer == nil else {
164 | return
165 | }
166 |
167 | timer = Timer.scheduledTimer(timeInterval: duration,
168 | target: self,
169 | selector: #selector(timerDidElapse(_:)),
170 | userInfo: nil, repeats: true)
171 | }
172 |
173 | /// Remove auto scrolling timer
174 | func destroyTimer() {
175 | guard timer != nil else {
176 | return
177 | }
178 |
179 | timer?.invalidate()
180 | timer = nil
181 | }
182 |
183 | /// Called when a scroll animation is finished
184 | func didFinishScrollIfEnabled() {
185 | guard isScrolling == true else {
186 | return
187 | }
188 |
189 | isScrolling = nil
190 | delegate?.autoScroller(didFinishScrollAnimation: self)
191 | }
192 |
193 | @objc func timerDidElapse(_ timer: Timer) {
194 | isScrolling = true
195 | delegate?.autoScroller(willBeginScrollAnimation: self)
196 | handler?.autoScroller(didRequestAutoScroll: self, animated: animateScroll)
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/Sources/Pageboy/AutoScrolling/PageboyViewController+AutoScrolling.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyViewController+AutoScrolling.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 17/05/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // MARK: - PageboyAutoScrollerHandler
12 | extension PageboyViewController: PageboyAutoScrollerHandler {
13 |
14 | func autoScroller(didRequestAutoScroll autoScroller: PageboyAutoScroller, animated: Bool) {
15 | scrollToPage(.next, animated: animated)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Model/NavigationDirection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyNavigationDirection.swift
3 | // Pageboy iOS
4 | //
5 | // Created by Merrick Sapsford on 30/04/2018.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension PageboyViewController {
12 |
13 | public enum NavigationDirection {
14 | case neutral
15 | case forward
16 | case reverse
17 | }
18 | }
19 | internal typealias NavigationDirection = PageboyViewController.NavigationDirection
20 |
21 | internal extension PageboyViewController.NavigationDirection {
22 |
23 | var rawValue: UIPageViewController.NavigationDirection {
24 | switch self {
25 |
26 | case .reverse:
27 | return .reverse
28 |
29 | default:
30 | return .forward
31 | }
32 | }
33 | }
34 |
35 | internal extension NavigationDirection {
36 |
37 | static func forPage(_ page: Int,
38 | previousPage: Int) -> NavigationDirection {
39 | return forPosition(CGFloat(page), previous: CGFloat(previousPage))
40 | }
41 |
42 | static func forPosition(_ position: CGFloat,
43 | previous previousPosition: CGFloat) -> NavigationDirection {
44 | if position == previousPosition {
45 | return .neutral
46 | }
47 | return position > previousPosition ? .forward : .reverse
48 | }
49 |
50 | static func forPageScroll(to newPage: Page,
51 | at index: Int,
52 | in pageViewController: PageboyViewController) -> NavigationDirection {
53 | var direction = NavigationDirection.forPage(index, previousPage: pageViewController.currentIndex ?? index)
54 |
55 | if pageViewController.isInfiniteScrollEnabled {
56 | switch newPage {
57 | case .next:
58 | direction = .forward
59 | case .previous:
60 | direction = .reverse
61 | default:
62 | break
63 | }
64 | }
65 |
66 | return direction
67 | }
68 | }
69 |
70 | internal extension NavigationDirection {
71 |
72 | func layoutNormalized(isRtL: Bool) -> NavigationDirection {
73 | guard isRtL else {
74 | return self
75 | }
76 | return self == .forward ? .reverse : .forward
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Model/Page.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Page.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 30/04/2018.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension PageboyViewController {
12 |
13 | /// A page index.
14 | public typealias PageIndex = Int
15 |
16 | /// The index of a page in the page view controller.
17 | ///
18 | /// - next: The next page if available.
19 | /// - previous: The previous page if available.
20 | /// - first: The first page.
21 | /// - last: The last page.
22 | /// - at: A custom specified page index.
23 | // swiftlint:disable identifier_name
24 | public enum Page {
25 | case next
26 | case previous
27 | case first
28 | case last
29 | case at(index: PageIndex)
30 | }
31 | }
32 | internal typealias Page = PageboyViewController.Page
33 | internal typealias PageIndex = PageboyViewController.PageIndex
34 |
35 | internal extension Page {
36 |
37 | /// PageIndex value for page.
38 | ///
39 | /// - Parameter pageViewController: PageboyViewController which contains page.
40 | /// - Returns: PageIndex value.
41 | func indexValue(in pageViewController: PageboyViewController) -> PageIndex {
42 | return Page.indexValue(for: self, in: pageViewController)
43 | }
44 |
45 | /// Convert a Page to a PageIndex.
46 | ///
47 | /// - Parameters:
48 | /// - page: Page to convert.
49 | /// - pageViewController: PageboyViewController which contains page.
50 | /// - Returns: Converted PageIndex.
51 | static func indexValue(for page: Page,
52 | in pageViewController: PageboyViewController) -> PageIndex {
53 | switch page {
54 |
55 | case .next:
56 | guard let currentIndex = pageViewController.currentIndex else {
57 | return 0
58 | }
59 | var proposedIndex = currentIndex + 1
60 | if pageViewController.isInfiniteScrollEnabled && proposedIndex == pageViewController.pageCount { // scroll back to first index
61 | proposedIndex = 0
62 | }
63 | return proposedIndex
64 |
65 | case .previous:
66 | guard let currentIndex = pageViewController.currentIndex else {
67 | return 0
68 | }
69 | var proposedIndex = currentIndex - 1
70 | if pageViewController.isInfiniteScrollEnabled && proposedIndex < 0 { // scroll to last index
71 | proposedIndex = (pageViewController.pageCount ?? 1) - 1
72 | }
73 | return proposedIndex
74 |
75 | case .first:
76 | return 0
77 |
78 | case .last:
79 | return (pageViewController.pageCount ?? 1) - 1
80 |
81 | case .at(let index):
82 | return index
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Pageboy.h:
--------------------------------------------------------------------------------
1 | //
2 | // Pageboy.h
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 25/07/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for Pageboy.
12 | FOUNDATION_EXPORT double PageboyVersionNumber;
13 |
14 | //! Project version string for Pageboy.
15 | FOUNDATION_EXPORT const unsigned char PageboyVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Sources/Pageboy/PageboyViewController+ScrollDetection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyScrollDetection.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 13/02/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // MARK: - UIPageViewControllerDelegate
12 | extension PageboyViewController: UIPageViewControllerDelegate {
13 |
14 | // MARK: UIPageViewControllerDelegate
15 |
16 | public func pageViewController(_ pageViewController: UIPageViewController,
17 | willTransitionTo pendingViewControllers: [UIViewController]) {
18 | guard pageViewControllerIsActual(pageViewController) else {
19 | return
20 | }
21 |
22 | self.pageViewController(pageViewController,
23 | willTransitionTo: pendingViewControllers,
24 | animated: false)
25 | }
26 |
27 | internal func pageViewController(_ pageViewController: UIPageViewController,
28 | willTransitionTo pendingViewControllers: [UIViewController],
29 | animated: Bool) {
30 | guard pageViewControllerIsActual(pageViewController) else {
31 | return
32 | }
33 |
34 | guard let viewController = pendingViewControllers.first,
35 | let index = viewControllerIndexMap.index(for: viewController) else {
36 | return
37 | }
38 |
39 | expectedTransitionIndex = index
40 | let direction = NavigationDirection.forPage(index, previousPage: currentIndex ?? index)
41 | delegate?.pageboyViewController(self,
42 | willScrollToPageAt: index,
43 | direction: direction,
44 | animated: animated)
45 | }
46 |
47 | public func pageViewController(_ pageViewController: UIPageViewController,
48 | didFinishAnimating finished: Bool,
49 | previousViewControllers: [UIViewController],
50 | transitionCompleted completed: Bool) {
51 | defer {
52 | expectedTransitionIndex = nil
53 | }
54 |
55 | guard pageViewControllerIsActual(pageViewController) else {
56 | return }
57 |
58 | guard completed else {
59 | guard let expectedIndex = expectedTransitionIndex else {
60 | return }
61 |
62 | guard let viewController = previousViewControllers.first,
63 | let previousIndex = viewControllerIndexMap.index(for: viewController) else {
64 | return
65 | }
66 |
67 | delegate?.pageboyViewController(self,
68 | didCancelScrollToPageAt: expectedIndex,
69 | returnToPageAt: previousIndex)
70 | return }
71 |
72 | guard let viewController = pageViewController.viewControllers?.first,
73 | let index = viewControllerIndexMap.index(for: viewController),
74 | index == expectedTransitionIndex else {
75 | return }
76 |
77 | updateCurrentPageIndexIfNeeded(index)
78 | }
79 | }
80 |
81 | // MARK: - UIScrollViewDelegate
82 | extension PageboyViewController: UIScrollViewDelegate {
83 |
84 | public func scrollViewDidScroll(_ scrollView: UIScrollView) {
85 | guard let currentIndex = currentIndex, scrollViewIsActual(scrollView) else {
86 | return
87 | }
88 | guard updateContentOffsetForBounceIfNeeded(scrollView: scrollView) == false else {
89 | return
90 | }
91 |
92 | guard let (newPosition, previousPosition) = calculateNewPagePosition(in: scrollView, currentIndex: currentIndex) else {
93 | return
94 | }
95 |
96 | // do not continue if a page change is detected
97 | let didDetectNewPage = detectNewPageIndexIfNeeded(pagePosition: newPosition,
98 | scrollView: scrollView)
99 | guard !didDetectNewPage else {
100 | return
101 | }
102 |
103 | // update page position for infinite overscroll if required
104 | let pagePosition: CGFloat
105 | if let infiniteAdjustedPosition = adjustedPagePositionForInfiniteOverscroll(from: newPosition) {
106 | pagePosition = infiniteAdjustedPosition
107 | } else {
108 | pagePosition = newPosition
109 | }
110 |
111 | // provide scroll updates
112 | var positionPoint: CGPoint!
113 | let direction = NavigationDirection.forPosition(pagePosition, previous: previousPosition)
114 | if navigationOrientation == .horizontal {
115 | positionPoint = CGPoint(x: pagePosition, y: scrollView.contentOffset.y)
116 | } else {
117 | positionPoint = CGPoint(x: scrollView.contentOffset.x, y: pagePosition)
118 | }
119 |
120 | // ignore duplicate updates
121 | guard currentPosition != positionPoint else {
122 | return
123 | }
124 | currentPosition = positionPoint
125 | delegate?.pageboyViewController(self,
126 | didScrollTo: positionPoint,
127 | direction: direction,
128 | animated: isScrollingAnimated)
129 | previousPagePosition = pagePosition
130 | }
131 |
132 | public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
133 | guard scrollViewIsActual(scrollView) else {
134 | return
135 | }
136 | if autoScroller.cancelsOnScroll {
137 | autoScroller.cancel()
138 | }
139 | }
140 |
141 | public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
142 | guard scrollViewIsActual(scrollView) else {
143 | return
144 | }
145 | self.scrollView(didEndScrolling: scrollView)
146 | }
147 |
148 | public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
149 | guard scrollViewIsActual(scrollView) else {
150 | return
151 | }
152 | self.scrollView(didEndScrolling: scrollView)
153 | }
154 |
155 | public func scrollViewWillEndDragging(_ scrollView: UIScrollView,
156 | withVelocity velocity: CGPoint,
157 | targetContentOffset: UnsafeMutablePointer) {
158 | guard scrollViewIsActual(scrollView) else {
159 | return
160 | }
161 |
162 | updateContentOffsetForBounceIfNeeded(scrollView: scrollView)
163 | }
164 |
165 | private func scrollView(didEndScrolling scrollView: UIScrollView) {
166 | guard scrollViewIsActual(scrollView) else {
167 | return
168 | }
169 |
170 | if autoScroller.restartsOnScrollEnd {
171 | autoScroller.restart()
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/Sources/Pageboy/PageboyViewController+Updating.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyViewController+Updating.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 31/03/2018.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension PageboyViewController {
12 |
13 | /// Behavior to evaluate after a page update.
14 | ///
15 | /// - doNothing: Do nothing.
16 | /// - scrollToUpdate: Scroll to the update.
17 | /// - scrollTo: Scroll to a specified index.
18 | public enum PageUpdateBehavior {
19 | case doNothing
20 | case scrollToUpdate
21 | case scrollTo(index: PageIndex)
22 | }
23 |
24 | internal enum UpdateOperation {
25 | case insert
26 | case delete
27 | }
28 | }
29 |
30 | // MARK: - Page Updates
31 | internal extension PageboyViewController {
32 |
33 | func performUpdates(for newIndex: PageIndex?,
34 | viewController: UIViewController?,
35 | update: (operation: UpdateOperation, behavior: PageUpdateBehavior),
36 | indexOperation: (_ currentIndex: PageIndex, _ newIndex: PageIndex) -> Void,
37 | completion: ((Bool) -> Void)?) {
38 | guard let newIndex = newIndex, let viewController = viewController else { // no view controller - reset
39 | updateViewControllers(to: [UIViewController()],
40 | animated: false,
41 | async: false,
42 | force: false,
43 | completion: completion)
44 | self.currentIndex = nil
45 | return
46 | }
47 |
48 | guard let currentIndex = currentIndex else { // if no `currentIndex` - currently have no pages - set VC and index.
49 | updateViewControllers(to: [viewController],
50 | animated: false,
51 | async: false,
52 | force: false,
53 | completion: completion)
54 | self.currentIndex = newIndex
55 | return
56 | }
57 |
58 | // If we are inserting a page that is lower/equal to the current index
59 | // we have to move the current page up therefore we can't just cross-dissolve.
60 | let isInsertionThatRequiresMoving = update.operation == .insert && newIndex <= currentIndex
61 |
62 | if !isInsertionThatRequiresMoving && newIndex == currentIndex { // currently on the page for the update.
63 | pageViewController?.view.crossDissolve(during: { [weak self, viewController] in
64 | self?.updateViewControllers(to: [viewController],
65 | animated: false,
66 | async: true,
67 | force: false,
68 | completion: completion)
69 | })
70 | } else { // update is happening on some other page.
71 | indexOperation(currentIndex, newIndex)
72 |
73 | // If we are deleting, check if the new index is greater than the current. If it is then we
74 | // dont need to do anything...
75 | if update.operation == .delete && newIndex > currentIndex {
76 | completion?(true)
77 | return
78 | }
79 |
80 | // Reload current view controller in UIPageViewController if insertion index is next/previous page.
81 | if pageIndex(newIndex, isNextTo: currentIndex) {
82 |
83 | let newViewController: UIViewController
84 | switch update.operation {
85 |
86 | case .insert:
87 | guard let currentViewController = currentViewController else {
88 | completion?(true)
89 | return
90 | }
91 | newViewController = currentViewController
92 |
93 | case .delete:
94 | newViewController = viewController
95 | }
96 |
97 | updateViewControllers(to: [newViewController], animated: false, async: true, force: false, completion: { [weak self, newIndex, update] _ in
98 | self?.performScrollUpdate(to: newIndex, behavior: update.behavior)
99 | completion?(true)
100 | })
101 | } else { // Otherwise just perform scroll update
102 | performScrollUpdate(to: newIndex, behavior: update.behavior)
103 | completion?(true)
104 | }
105 | }
106 | }
107 | }
108 |
109 | // MARK: - Utilities
110 | extension PageboyViewController {
111 |
112 | func verifyNewPageCount(then update: (Int, Int) -> Void) {
113 | guard let oldPageCount = pageCount,
114 | let newPageCount = dataSource?.numberOfViewControllers(in: self) else {
115 | return
116 | }
117 | update(oldPageCount, newPageCount)
118 | }
119 |
120 | func performScrollUpdate(to update: PageIndex, behavior: PageUpdateBehavior) {
121 | switch behavior {
122 |
123 | case .scrollToUpdate:
124 | scrollToPage(.at(index: update), animated: true)
125 |
126 | case .scrollTo(let index):
127 | scrollToPage(.at(index: index), animated: true)
128 |
129 | default:
130 | break
131 | }
132 | }
133 |
134 | func pageIndex(_ index: PageIndex, isNextTo other: PageIndex) -> Bool {
135 | return index - other == 1 || other - index == 1
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/Sources/Pageboy/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyTracking
6 |
7 | NSPrivacyAccessedAPITypes
8 |
9 | NSPrivacyCollectedDataTypes
10 |
11 | NSPrivacyTrackingDomains
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Protocols/PageboyViewControllerDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyViewControllerDataSource.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 24/11/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol PageboyViewControllerDataSource: AnyObject {
12 |
13 | /// The number of view controllers to display.
14 | ///
15 | /// - Parameter pageboyViewController: The Page view controller.
16 | /// - Returns: The total number of view controllers to display.
17 | func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int
18 |
19 | /// The view controller to display at a page index.
20 | ///
21 | /// - Parameters:
22 | /// - pageboyViewController: The Page view controller.
23 | /// - index: The page index.
24 | /// - Returns: The view controller to display
25 | func viewController(for pageboyViewController: PageboyViewController,
26 | at index: PageboyViewController.PageIndex) -> UIViewController?
27 |
28 | /// The default page index to display in the Pageboy view controller.
29 | ///
30 | /// - Parameter pageboyViewController: The Pageboy view controller
31 | /// - Returns: Default page
32 | func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page?
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Protocols/PageboyViewControllerDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyViewControllerDelegate.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 24/11/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol PageboyViewControllerDelegate: AnyObject {
12 |
13 | /// The page view controller will begin scrolling to a new page.
14 | ///
15 | /// - Parameters:
16 | /// - pageboyViewController: The Page view controller.
17 | /// - index: The new page index.
18 | /// - direction: The direction of the scroll.
19 | /// - animation: Whether the scroll will be animated.
20 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
21 | willScrollToPageAt index: PageboyViewController.PageIndex,
22 | direction: PageboyViewController.NavigationDirection,
23 | animated: Bool)
24 |
25 | /// The page view controller did scroll to an offset between pages.
26 | ///
27 | /// - Parameters:
28 | /// - pageboyViewController: The Page view controller.
29 | /// - position: The current relative page position.
30 | /// - direction: The direction of the scroll.
31 | /// - animated: Whether the scroll is being animated.
32 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
33 | didScrollTo position: CGPoint,
34 | direction: PageboyViewController.NavigationDirection,
35 | animated: Bool)
36 |
37 | /// The page view controller did not (!) complete scroll to a new page.
38 | ///
39 | /// - Parameters:
40 | /// - pageboyViewController: The Page view controller.
41 | /// - index: The expected new page index, that was not (!) scrolled to.
42 | /// - previousIndex: The page index returned to.
43 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
44 | didCancelScrollToPageAt index: PageboyViewController.PageIndex,
45 | returnToPageAt previousIndex: PageboyViewController.PageIndex)
46 |
47 | /// The page view controller did complete scroll to a new page.
48 | ///
49 | /// - Parameters:
50 | /// - pageboyViewController: The Page view controller.
51 | /// - index: The new page index.
52 | /// - direction: The direction of the scroll.
53 | /// - animation: Whether the scroll was animated.
54 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
55 | didScrollToPageAt index: PageboyViewController.PageIndex,
56 | direction: PageboyViewController.NavigationDirection,
57 | animated: Bool)
58 |
59 | /// The page view controller did reload.
60 | ///
61 | /// - Parameters:
62 | /// - pageboyViewController: The Pageboy view controller.
63 | /// - currentViewController: The current view controller.
64 | /// - currentPageIndex: The current page index.
65 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
66 | didReloadWith currentViewController: UIViewController,
67 | currentPageIndex: PageboyViewController.PageIndex)
68 | }
69 |
70 | public extension PageboyViewControllerDelegate {
71 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
72 | didCancelScrollToPageAt index: PageboyViewController.PageIndex,
73 | returnToPageAt previousIndex: PageboyViewController.PageIndex) {
74 | // Default implementation
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Transitioning/PageboyViewController+Transitioning.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyViewController+Transitioning.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 29/05/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // MARK: - PageboyViewController transition configuration.
12 | extension PageboyViewController {
13 |
14 | /// Transition for a page scroll.
15 | public final class Transition {
16 |
17 | /// Style for the transition.
18 | ///
19 | /// - push: Slide the new page in (Default).
20 | /// - fade: Fade the new page in.
21 | /// - moveIn: Move the new page in over the top of the current page.
22 | /// - reveal: Reveal the new page under the current page.
23 | public enum Style: String {
24 | case push
25 | case fade
26 | case moveIn
27 | case reveal
28 | }
29 |
30 | /// The style for the transition.
31 | public let style: Style
32 | /// The duration of the transition.
33 | public let duration: TimeInterval
34 |
35 | // MARK: Init
36 |
37 | /// Initialize a transition.
38 | ///
39 | /// - Parameters:
40 | /// - style: The style to use.
41 | /// - duration: The duration to transition for.
42 | public init(style: Style, duration: TimeInterval) {
43 | self.style = style
44 | self.duration = duration
45 | }
46 | }
47 | }
48 |
49 | // MARK: - Custom PageboyViewController transitioning.
50 | internal extension PageboyViewController {
51 |
52 | // MARK: Set Up
53 |
54 | private func prepareForTransition() {
55 | guard transitionDisplayLink == nil else {
56 | return
57 | }
58 |
59 | let displayLink = CADisplayLink(target: self, selector: #selector(displayLinkDidTick))
60 | displayLink.isPaused = true
61 | displayLink.add(to: .main, forMode: .common)
62 | transitionDisplayLink = displayLink
63 | }
64 |
65 | private func clearUpAfterTransition() {
66 | transitionDisplayLink?.invalidate()
67 | transitionDisplayLink = nil
68 | }
69 |
70 | // MARK: Animation
71 |
72 | @objc func displayLinkDidTick() {
73 | self.activeTransitionOperation?.tick()
74 | }
75 |
76 | /// Perform a transition to a new page index.
77 | ///
78 | /// - Parameters:
79 | /// - from: The current index.
80 | /// - to: The new index.
81 | /// - direction: The direction of travel.
82 | /// - animated: Whether to animate the transition.
83 | /// - completion: Action on the completion of the transition.
84 | func performTransition(from startIndex: Int,
85 | to endIndex: Int,
86 | with direction: NavigationDirection,
87 | animated: Bool,
88 | completion: @escaping TransitionOperation.Completion) {
89 |
90 | guard let transition = transition, animated == true, activeTransitionOperation == nil else {
91 | completion(false)
92 | return
93 | }
94 | guard let scrollView = pageViewController?.scrollView else {
95 | #if DEBUG
96 | fatalError("Can't find UIPageViewController scroll view")
97 | #else
98 | return
99 | #endif
100 | }
101 |
102 | prepareForTransition()
103 |
104 | let navigationOrientation = self.navigationOrientation
105 |
106 | /// Calculate semantic direction for RtL languages
107 | var semanticDirection = direction
108 | if view.layoutIsRightToLeft && navigationOrientation == .horizontal {
109 | semanticDirection = direction.layoutNormalized(isRtL: view.layoutIsRightToLeft)
110 | }
111 |
112 | // create a transition and unpause display link
113 | let action = TransitionOperation.Action(startIndex: startIndex,
114 | endIndex: endIndex,
115 | direction: direction,
116 | semanticDirection: semanticDirection,
117 | orientation: navigationOrientation)
118 | activeTransitionOperation = TransitionOperation(for: transition,
119 | action: action,
120 | delegate: self)
121 | transitionDisplayLink?.isPaused = false
122 |
123 | // start transition
124 | activeTransitionOperation?.start(on: scrollView.layer,
125 | completion: completion)
126 | }
127 | }
128 |
129 | extension PageboyViewController: TransitionOperationDelegate {
130 |
131 | func transitionOperation(_ operation: TransitionOperation,
132 | didFinish finished: Bool) {
133 | transitionDisplayLink?.isPaused = true
134 | activeTransitionOperation = nil
135 |
136 | clearUpAfterTransition()
137 | }
138 |
139 | func transitionOperation(_ operation: TransitionOperation,
140 | didUpdateWith percentComplete: CGFloat) {
141 |
142 | let isReverse = operation.action.direction == .reverse
143 | let isVertical = operation.action.orientation == .vertical
144 |
145 | /// Take into account the diff between startIndex and endIndex
146 | let indexDiff = abs(operation.action.endIndex - operation.action.startIndex)
147 | let diff = percentComplete * CGFloat(indexDiff)
148 |
149 | let index = CGFloat(currentIndex ?? 0)
150 | let position = isReverse ? index - diff : index + diff
151 | let point = CGPoint(x: isVertical ? 0.0 : position,
152 | y: isVertical ? position : 0.0)
153 |
154 | currentPosition = point
155 | delegate?.pageboyViewController(self, didScrollTo: point,
156 | direction: operation.action.direction,
157 | animated: true)
158 | previousPagePosition = position
159 | }
160 | }
161 |
162 | internal extension CATransition {
163 |
164 | func configure(from: PageboyViewController.Transition) {
165 | duration = from.duration
166 | type = CATransitionType(rawValue: from.style.rawValue)
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Transitioning/TransitionOperation+Action.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TransitionOperation+Action.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 30/05/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | internal extension TransitionOperation {
12 |
13 | /// Action that occurs in an operation.
14 | struct Action {
15 |
16 | /// The page start index.
17 | let startIndex: Int
18 | /// The page end index.
19 | let endIndex: Int
20 | /// The direction of travel.
21 | let direction: NavigationDirection
22 | /// The semantic direction of travel. In RtL languages,
23 | /// this will be the opposite of direction on the horizontal axis.
24 | let semanticDirection: NavigationDirection
25 | /// The orientation of the page view controller.
26 | let orientation: UIPageViewController.NavigationOrientation
27 |
28 | }
29 | }
30 |
31 | internal extension TransitionOperation.Action {
32 |
33 | /// Animation sub-type for the action.
34 | var transitionSubType: CATransitionSubtype {
35 | switch orientation {
36 |
37 | case .horizontal:
38 | switch semanticDirection {
39 |
40 | case .reverse:
41 | return .fromLeft
42 | default:
43 | return .fromRight
44 | }
45 |
46 | case .vertical:
47 | switch semanticDirection {
48 |
49 | case .reverse:
50 | return .fromBottom
51 | default:
52 | return .fromTop
53 | }
54 |
55 | @unknown default:
56 | fatalError("unsupported orientation \(orientation.rawValue)")
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Transitioning/TransitionOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TransitionOperation.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 29/05/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | internal protocol TransitionOperationDelegate: AnyObject {
12 |
13 | /// A transition operation did finish.
14 | ///
15 | /// - Parameters:
16 | /// - operation: The operation.
17 | /// - finished: Whether it successfully finished.
18 | func transitionOperation(_ operation: TransitionOperation,
19 | didFinish finished: Bool)
20 |
21 | /// A transition operation did progress.
22 | ///
23 | /// - Parameters:
24 | /// - operation: The operation.
25 | /// - percentComplete: The percent that the operation is complete.
26 | func transitionOperation(_ operation: TransitionOperation,
27 | didUpdateWith percentComplete: CGFloat)
28 | }
29 |
30 | /// An operation for performing a PageboyViewController transition
31 | internal class TransitionOperation: NSObject, CAAnimationDelegate {
32 |
33 | // MARK: Types
34 |
35 | /// Operation completion action.
36 | typealias Completion = (Bool) -> Void
37 |
38 | // MARK: Properties
39 |
40 | /// The transition for the operation.
41 | let transition: PageboyViewController.Transition
42 | /// The action that is occuring as part of the transition.
43 | let action: Action
44 |
45 | /// The raw animation for the operation.
46 | private var animation: CATransition?
47 | /// The time that the operation did start.
48 | private(set) var startTime: CFTimeInterval?
49 |
50 | /// Whether the operation is currently animating.
51 | private var isAnimating: Bool = false
52 |
53 | /// The object that acts as a delegate to the operation
54 | private(set) weak var delegate: TransitionOperationDelegate?
55 | /// Action to execute when the operation is complete.
56 | private var completion: Completion?
57 |
58 | /// The total duration of the transition.
59 | var duration: CFTimeInterval {
60 | guard let animation = animation else {
61 | return 0.0
62 | }
63 | return animation.duration
64 | }
65 | /// The percent that the transition is complete.
66 | var percentComplete: CGFloat {
67 | guard isAnimating else {
68 | return 0.0
69 | }
70 |
71 | let percent = CGFloat((CACurrentMediaTime() - (startTime ?? CACurrentMediaTime())) / duration)
72 | return max(0.0, min(1.0, percent))
73 | }
74 |
75 | // MARK: Init
76 |
77 | init(for transition: PageboyViewController.Transition,
78 | action: Action,
79 | delegate: TransitionOperationDelegate) {
80 |
81 | self.action = action
82 | self.delegate = delegate
83 | self.transition = transition
84 |
85 | let animation = CATransition()
86 | animation.startProgress = 0.0
87 | animation.endProgress = 1.0
88 | animation.configure(from: transition)
89 |
90 | animation.subtype = action.transitionSubType
91 | animation.fillMode = .backwards
92 | self.animation = animation
93 |
94 | super.init()
95 |
96 | animation.delegate = self
97 | }
98 |
99 | // MARK: Transitioning
100 |
101 | /// Start the transition animation on a layer.
102 | ///
103 | /// - Parameter layer: The layer to animate.
104 | /// - Parameter completion: Completion of the transition.
105 | func start(on layer: CALayer,
106 | completion: @escaping Completion) {
107 | guard let animation = animation else {
108 | completion(false)
109 | return
110 | }
111 |
112 | self.completion = completion
113 | self.startTime = CACurrentMediaTime()
114 | layer.add(animation,
115 | forKey: "transition")
116 | }
117 |
118 | /// Perform a frame tick on the transition.
119 | func tick() {
120 | guard isAnimating else {
121 | return
122 | }
123 | delegate?.transitionOperation(self, didUpdateWith: percentComplete)
124 | }
125 |
126 | // MARK: CAAnimationDelegate
127 |
128 | public func animationDidStart(_ anim: CAAnimation) {
129 | isAnimating = true
130 | }
131 |
132 | public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
133 | isAnimating = false
134 | completion?(flag)
135 | delegate?.transitionOperation(self, didFinish: flag)
136 | animation = nil
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/Sources/Pageboy/UIViewController+Pageboy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+Pageboy.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 18/06/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIViewController {
12 |
13 | /// The parent PageboyViewController.
14 | /// Available from any direct child view controllers within a PageboyViewController.
15 | /// Deprecated in Pageboy 3.1.0.
16 | @available(*, deprecated, message: "Use pageboyParent")
17 | public var parentPageboy: PageboyViewController? {
18 | return pageboyParent
19 | }
20 |
21 | /// The parent PageboyViewController.
22 | /// Available from any direct child view controllers within a PageboyViewController.
23 | public var pageboyParent: PageboyViewController? {
24 | return parent?.parent as? PageboyViewController
25 | }
26 |
27 | /// Page index for this view controller if it's embedded in a PageboyViewController.
28 | public var pageboyPageIndex: PageboyViewController.PageIndex? {
29 | return pageboyParent?.pageIndex(of: self)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Utilities/Extensions/DispatchQueue+main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DispatchQueue+main.swift
3 | // Pageboy
4 | //
5 | // Created by Remi Robert on 2019/02/11.
6 | // Copyright © 2019 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension DispatchQueue {
12 |
13 | static func executeInMainThread(callback: @escaping () -> Void) {
14 | if Thread.isMainThread {
15 | callback()
16 | } else {
17 | DispatchQueue.main.sync(execute: callback)
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Utilities/Extensions/UIApplication+SafeShared.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIApplication+SafeShared.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 05/01/2018.
6 | // Copyright © 2018 Merrick Sapsford. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | internal extension UIApplication {
12 |
13 | static var safeShared: UIApplication? {
14 | guard #available(iOSApplicationExtension 8, *) else {
15 | return nil
16 | }
17 |
18 | guard UIApplication.responds(to: NSSelectorFromString("sharedApplication")),
19 | let unmanagedSharedApplication = UIApplication.perform(NSSelectorFromString("sharedApplication")) else {
20 | return nil
21 | }
22 |
23 | return unmanagedSharedApplication.takeUnretainedValue() as? UIApplication
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Utilities/Extensions/UIPageViewController+ScrollView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIPageViewController+ScrollView.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 13/02/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | internal extension UIPageViewController {
12 |
13 | var scrollView: UIScrollView? {
14 | for subview in view.subviews {
15 | if let scrollView = subview as? UIScrollView {
16 | return scrollView
17 | }
18 | }
19 | return nil
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Utilities/Extensions/UIScrollView+Interaction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIScrollView+Interaction.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 23/01/2018.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | internal extension UIScrollView {
12 |
13 | /// Whether the scroll view can be assumed to be interactively scrolling
14 | var isProbablyActiveInScroll: Bool {
15 | return isTracking || isDecelerating
16 | }
17 |
18 | func cancelTouches() {
19 | panGestureRecognizer.isEnabled = false
20 | panGestureRecognizer.isEnabled = true
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Utilities/Extensions/UIView+Animation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Animation.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 26/04/2018.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView {
12 |
13 | func crossDissolve(duration: TimeInterval = 0.25,
14 | during animations: @escaping () -> Void,
15 | completion: ((Bool) -> Void)? = nil) {
16 | UIView.transition(with: self,
17 | duration: duration,
18 | options: .transitionCrossDissolve,
19 | animations: animations,
20 | completion: completion)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Utilities/Extensions/UIView+AutoLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+AutoLayout.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 15/02/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | internal extension UIView {
12 |
13 | @discardableResult
14 | func pinToSuperviewEdges(priority: UILayoutPriority = .required) -> [NSLayoutConstraint] {
15 | guard let superview = guardForSuperview() else {
16 | #if DEBUG
17 | fatalError("Could not fetch superview in pinToSuperviewEdges")
18 | #else
19 | return [NSLayoutConstraint]()
20 | #endif
21 | }
22 |
23 |
24 |
25 | return addConstraints(priority: priority, { () -> [NSLayoutConstraint] in
26 | return [
27 | topAnchor.constraint(equalTo: superview.topAnchor),
28 | leadingAnchor.constraint(equalTo: superview.leadingAnchor),
29 | bottomAnchor.constraint(equalTo: superview.bottomAnchor),
30 | trailingAnchor.constraint(equalTo: superview.trailingAnchor)
31 | ]
32 | })
33 | }
34 |
35 | @discardableResult
36 | func matchWidth(to view: UIView,
37 | priority: UILayoutPriority = .required) -> NSLayoutConstraint? {
38 | let constraints = addConstraints(priority: priority, { () -> [NSLayoutConstraint] in
39 | return [NSLayoutConstraint(item: self,
40 | attribute: .width,
41 | relatedBy: .equal,
42 | toItem: view,
43 | attribute: .width,
44 | multiplier: 1.0,
45 | constant: 0.0)]
46 | })
47 |
48 | guard let constraint = constraints.first else {
49 | #if DEBUG
50 | fatalError("Could not add matchWidth constraint")
51 | #else
52 | return nil
53 | #endif
54 | }
55 | return constraint
56 | }
57 |
58 | @discardableResult
59 | func matchHeight(to view: UIView,
60 | priority: UILayoutPriority = .required) -> NSLayoutConstraint? {
61 | let constraints = addConstraints(priority: priority, { () -> [NSLayoutConstraint] in
62 | return [NSLayoutConstraint(item: self,
63 | attribute: .height,
64 | relatedBy: .equal,
65 | toItem: view,
66 | attribute: .height,
67 | multiplier: 1.0,
68 | constant: 0.0)]
69 | })
70 |
71 | guard let constraint = constraints.first else {
72 | #if DEBUG
73 | fatalError("Could not add matchHeight constraint")
74 | #else
75 | return nil
76 | #endif
77 | }
78 | return constraint
79 | }
80 |
81 | // MARK: Utilities
82 |
83 | private func prepareForAutoLayout(_ completion: () -> Void) {
84 | translatesAutoresizingMaskIntoConstraints = false
85 | completion()
86 | }
87 |
88 | @discardableResult
89 | private func addConstraints(priority: UILayoutPriority, _ completion: () -> [NSLayoutConstraint]) -> [NSLayoutConstraint] {
90 | let constraints = completion()
91 | constraints.forEach({ $0.priority = priority })
92 | prepareForAutoLayout {
93 | NSLayoutConstraint.activate(constraints)
94 | }
95 | return constraints
96 | }
97 |
98 | private func guardForSuperview() -> UIView? {
99 | guard let superview = superview else {
100 | #if DEBUG
101 | fatalError("No superview for view \(self)")
102 | #else
103 | return nil
104 | #endif
105 | }
106 | return superview
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Utilities/Extensions/UIView+Localization.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Localization.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 18/06/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView {
12 |
13 | /// Whether the layout direction of the view is right to left.
14 | var layoutIsRightToLeft: Bool {
15 | var layoutDirection: UIUserInterfaceLayoutDirection!
16 | DispatchQueue.executeInMainThread {
17 | layoutDirection = self.getUserInterfaceLayoutDirection()
18 | }
19 | return layoutDirection == .rightToLeft
20 | }
21 |
22 | private func getUserInterfaceLayoutDirection() -> UIUserInterfaceLayoutDirection {
23 | return UIView.userInterfaceLayoutDirection(for: semanticContentAttribute)
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Utilities/IndexedObjectMap.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjectIndexMap.swift
3 | // Pageboy iOS
4 | //
5 | // Created by Merrick Sapsford on 02/03/2019.
6 | // Copyright © 2019 UI At Six. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Map which weakly stores an object for an index key.
12 | internal final class IndexedObjectMap {
13 |
14 | // MARK: Properties
15 |
16 | private var map = [Int: WeakWrapper]()
17 |
18 | // MARK: Accessors
19 |
20 | func index(for object: T) -> Int? {
21 | cleanUp()
22 | return map.first(where: { $0.value.object === object })?.key
23 | }
24 |
25 | // MARK: Mutators
26 |
27 | func set(_ index: Int, for object: T) {
28 | cleanUp()
29 |
30 | let wrapper = WeakWrapper(object)
31 | map[index] = wrapper
32 | }
33 |
34 | func removeAll() {
35 | map.removeAll()
36 | }
37 |
38 | private func cleanUp() {
39 | var invalidIndexes = [Int]()
40 | map.forEach({
41 | if $0.value.object == nil {
42 | invalidIndexes.append($0.key)
43 | }
44 | })
45 | invalidIndexes.forEach({ self.map[$0] = nil })
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Utilities/PatchedPageViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PatchedPageViewController.swift
3 | // Pageboy
4 | //
5 | // Created by Arabia -IT on 8/25/19.
6 | //
7 |
8 | import UIKit
9 |
10 | /// Fixes not updating dataSource on animated setViewControllers. See: https://stackoverflow.com/a/13253884/715593
11 | internal class PatchedPageViewController: UIPageViewController {
12 |
13 | private var isSettingViewControllers = false
14 |
15 | override func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewController.NavigationDirection, animated: Bool, completion: ((Bool) -> Void)? = nil) {
16 | guard !isSettingViewControllers else {
17 | completion?(false)
18 | return
19 | }
20 | isSettingViewControllers = true
21 | super.setViewControllers(viewControllers, direction: direction, animated: animated) { (isFinished) in
22 | if isFinished && animated {
23 | DispatchQueue.main.async {
24 | super.setViewControllers(viewControllers, direction: direction, animated: false, completion: { _ in
25 | self.isSettingViewControllers = false
26 | })
27 | }
28 | } else {
29 | self.isSettingViewControllers = false
30 | }
31 | completion?(isFinished)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/Pageboy/Utilities/WeakContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WeakContainer.swift
3 | // Pageboy iOS
4 | //
5 | // Created by Merrick Sapsford on 02/03/2019.
6 | // Copyright © 2019 UI At Six. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | internal final class WeakWrapper {
12 |
13 | private(set) weak var object: T?
14 |
15 | init(_ object: T) {
16 | self.object = object
17 | }
18 | }
19 |
20 | extension WeakWrapper: Equatable {
21 |
22 | static func == (lhs: WeakWrapper, rhs: WeakWrapper) -> Bool {
23 | return lhs.object === rhs.object
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/PageboyAutoScrollTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyAutoScrollTests.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 08/03/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pageboy
11 |
12 | class PageboyAutoScrollTests: PageboyTests {
13 |
14 | var autoScrollExpectation: XCTestExpectation?
15 |
16 | /// Test that PageboyAutoScroller enables correctly.
17 | func testAutoScrollEnabling() {
18 | self.dataSource.numberOfPages = 3
19 | self.pageboyViewController.dataSource = self.dataSource
20 |
21 | let currentIndex = self.pageboyViewController.currentIndex ?? 0
22 | let duration = self.pageboyViewController.autoScroller.intermissionDuration
23 |
24 | self.autoScrollExpectation = expectation(description: "autoScroll")
25 |
26 | self.pageboyViewController.autoScroller.animateScroll = false
27 | self.pageboyViewController.autoScroller.delegate = self
28 | self.pageboyViewController.autoScroller.enable(withIntermissionDuration: .custom(duration: 3.0))
29 |
30 | self.waitForExpectations(timeout: duration.rawValue) { (error) in
31 | XCTAssertNil(error, "Something went wrong")
32 | XCTAssert(self.pageboyViewController.currentIndex == currentIndex + 1,
33 | "PageboyAutoScroller does not auto scroll correctly when enabled.")
34 | }
35 | }
36 |
37 | /// Test that PageboyAutoScroller disables correctly.
38 | func testAutoScrollDisable() {
39 | self.dataSource.numberOfPages = 3
40 | self.pageboyViewController.dataSource = self.dataSource
41 |
42 | self.pageboyViewController.autoScroller.enable(withIntermissionDuration: .long)
43 | self.pageboyViewController.autoScroller.disable()
44 |
45 | XCTAssert(self.pageboyViewController.autoScroller.isEnabled == false &&
46 | self.pageboyViewController.autoScroller.wasCancelled != true,
47 | "PageboyAutoScroller does not disable correctly.")
48 | }
49 |
50 | /// Test that PageboyAutoScroller supports cancellation.
51 | func testAutoScrollCancellation() {
52 | self.dataSource.numberOfPages = 3
53 | self.pageboyViewController.dataSource = self.dataSource
54 |
55 | self.pageboyViewController.autoScroller.enable(withIntermissionDuration: .long)
56 | self.pageboyViewController.autoScroller.cancel()
57 |
58 | XCTAssert(self.pageboyViewController.autoScroller.isEnabled == false &&
59 | self.pageboyViewController.autoScroller.wasCancelled == true,
60 | "PageboyAutoScroller does not allow cancellation correctly.")
61 | }
62 |
63 | /// Test that PageboyAutoScroller supports restarting.
64 | func testAutoScrollRestart() {
65 | self.dataSource.numberOfPages = 3
66 | self.pageboyViewController.dataSource = self.dataSource
67 |
68 | self.pageboyViewController.autoScroller.enable(withIntermissionDuration: .long)
69 | self.pageboyViewController.autoScroller.cancel()
70 |
71 | let isEnabled = self.pageboyViewController.autoScroller.isEnabled
72 | let wasEnabled = self.pageboyViewController.autoScroller.wasCancelled ?? false
73 |
74 | self.pageboyViewController.autoScroller.restart()
75 |
76 | XCTAssertTrue(self.pageboyViewController.autoScroller.isEnabled && !isEnabled && wasEnabled,
77 | "PageboyAutoScroller does not allow restarting correctly.")
78 | }
79 | }
80 |
81 | extension PageboyAutoScrollTests: PageboyAutoScrollerDelegate {
82 |
83 | func autoScroller(willBeginScrollAnimation autoScroller: PageboyAutoScroller) {
84 |
85 | }
86 |
87 | func autoScroller(didFinishScrollAnimation autoScroller: PageboyAutoScroller) {
88 | autoScrollExpectation?.fulfill()
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/PageboyConfigurationTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyConfigurationTests.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 15/02/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pageboy
11 |
12 | class PageboyConfigurationTests: PageboyTests {
13 |
14 | /// Test updating navigationOrientation updates pageViewController correctly.
15 | func testPageboyNavigationOrientationChange() {
16 | self.pageboyViewController.navigationOrientation = .vertical
17 |
18 | XCTAssert(self.pageboyViewController.pageViewController?.navigationOrientation == .vertical,
19 | "Could not configure Pageboy navigationOrientation correctly")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/PageboyDataSourceTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyDataSourceTests.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 15/02/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pageboy
11 |
12 | class PageboyDataSourceTests: PageboyTests {
13 |
14 | /// Test loading view controllers from the data source.
15 | func testPageboyViewControllerValidSetUp() {
16 | self.dataSource.numberOfPages = 1
17 | self.pageboyViewController.dataSource = self.dataSource
18 |
19 | XCTAssert(self.pageboyViewController.pageCount == 1,
20 | "View Controllers were not successfully loaded from the data source.")
21 | }
22 |
23 | /// Test loading an empty view controller array from the data source.
24 | func testPageboyViewControllerEmptySetUp() {
25 | self.dataSource.numberOfPages = 0
26 | self.pageboyViewController.dataSource = self.dataSource
27 |
28 | XCTAssert(self.pageboyViewController.pageCount == 0,
29 | "Empty view controller array not successfully loaded from the data source.")
30 | }
31 |
32 | /// Test loading a nil array from the data source.
33 | func testPageboyViewControllerNilSetUp() {
34 | self.pageboyViewController.dataSource = self.dataSource
35 |
36 | XCTAssertNil(self.pageboyViewController.currentViewController,
37 | "Current view controller is not nil when data source returns nil.")
38 | }
39 |
40 | /// Test using the default .first PageIndex when nil returned
41 | /// in defaultPageIndex(forPageboyViewController:).
42 | func testPageboyViewControllerDefaultPageIndexDefault() {
43 | self.dataSource.numberOfPages = 3
44 | self.pageboyViewController.dataSource = self.dataSource
45 |
46 | XCTAssert(self.pageboyViewController.currentIndex == 0,
47 | "Default Page index is not using correct .first PageIndex when no value is returned.")
48 | }
49 |
50 | /// Test using a custom PageIndex when returned
51 | /// in defaultPageIndex(forPageboyViewController:).
52 | func testPageboyViewControllerDefaultPageIndexCustom() {
53 | self.dataSource.numberOfPages = 3
54 | self.dataSource.defaultIndex = .at(index: 1)
55 | self.pageboyViewController.dataSource = self.dataSource
56 |
57 | XCTAssert(self.pageboyViewController.currentIndex == 1,
58 | "Default page index is not using correct index when specified.")
59 | }
60 |
61 | /// Test using a custom out of range PageIndex when returned
62 | /// in defaultPageIndex(forPageboyViewController:).
63 | func testPageboyViewControllerDefaultPageIndexOutOfRange() {
64 | self.dataSource.numberOfPages = 3
65 | self.dataSource.defaultIndex = .at(index: 4)
66 | self.pageboyViewController.dataSource = self.dataSource
67 |
68 | XCTAssertNil(self.pageboyViewController.currentIndex,
69 | "Default page index is not correctly erroring when out of range index specified.")
70 | }
71 |
72 | /// Test using an invalid .next PageIndex when returned
73 | /// in defaultPageIndex(forPageboyViewController:).
74 | func testPageboyViewControllerDefaultPageIndexInvalid() {
75 | self.dataSource.numberOfPages = 3
76 | self.dataSource.defaultIndex = .next
77 | self.pageboyViewController.dataSource = self.dataSource
78 |
79 | XCTAssert(self.pageboyViewController.currentIndex == 0,
80 | "Default page index is not correctly handling an invalid index specified.")
81 | }
82 |
83 | /// Test using .first PageIndex when returned
84 | /// in defaultPageIndex(forPageboyViewController:).
85 | func testPageboyViewControllerDefaultPageIndexFirst() {
86 | self.dataSource.numberOfPages = 3
87 | self.dataSource.defaultIndex = .first
88 | self.pageboyViewController.dataSource = self.dataSource
89 |
90 | XCTAssert(self.pageboyViewController.currentIndex == 0,
91 | "Default Page index is not using correct .first PageIndex when specified.")
92 | }
93 |
94 | /// Test using .last PageIndex when returned
95 | /// in defaultPageIndex(forPageboyViewController:).
96 | func testPageboyViewControllerDefaultPageIndexLast() {
97 | self.dataSource.numberOfPages = 3
98 | self.dataSource.defaultIndex = .last
99 | self.pageboyViewController.dataSource = self.dataSource
100 |
101 | XCTAssert(self.pageboyViewController.currentIndex == 2,
102 | "Default Page index is not using correct .last PageIndex when specified.")
103 | }
104 |
105 | /// Test whether reloadPages fully reloads
106 | /// PageboyViewController.
107 | func testPageboyViewControllerReloadBehavior() {
108 | self.dataSource.numberOfPages = 5
109 | self.pageboyViewController.dataSource = self.dataSource
110 | let initialPageCount = self.pageboyViewController.pageCount
111 |
112 | self.dataSource.numberOfPages = 3
113 | self.pageboyViewController.reloadData()
114 | let reloadPageCount = self.pageboyViewController.pageCount
115 |
116 | XCTAssert(initialPageCount == 5 && reloadPageCount == 3,
117 | "reloadPages is not correctly reloading view controllers.")
118 | }
119 |
120 | /// Test that reloadPages successfully calls
121 | /// appropriate delegate function.
122 | func testPageboyViewControllerReloadDelegate() {
123 | self.dataSource.numberOfPages = 5
124 | self.pageboyViewController.dataSource = self.dataSource
125 |
126 | self.dataSource.numberOfPages = 3
127 | self.pageboyViewController.reloadData()
128 |
129 | let reloadPageCount = self.delegate.lastDidReloadPageCount
130 |
131 | XCTAssertTrue(reloadPageCount == 3,
132 | "reloadPages does not call didReloadViewControllers delegate function.")
133 | }
134 |
135 | /// Test that reloadCurrentPageSoftly does not cause a data source reload.
136 | func testPageboyViewControllerSoftReloadBehavior() {
137 | self.dataSource.numberOfPages = 5
138 | self.pageboyViewController.dataSource = self.dataSource
139 |
140 | self.pageboyViewController.isInfiniteScrollEnabled = true
141 |
142 | XCTAssertTrue(self.delegate.reloadCount == 1,
143 | "reloadCurrentPageSoftly causes the data source to reload")
144 | }
145 |
146 | /// Test that the UIPageViewController data source is
147 | /// returning correct pageViewController:viewControllerAfter: values.
148 | func testPageViewControllerDataSourceNextController() {
149 | self.dataSource.numberOfPages = 3
150 | self.pageboyViewController.dataSource = self.dataSource
151 |
152 | let viewController = self.dataSource.viewControllers![0]
153 | let nextViewController = self.pageboyViewController.pageViewController(self.pageboyViewController.pageViewController!,
154 | viewControllerAfter: viewController)
155 |
156 | XCTAssert(nextViewController === self.dataSource.viewControllers?[1],
157 | "pageViewController:viewControllerAfter is returning an incorrect view controller")
158 | }
159 |
160 | /// Test that the UIPageViewController data source is
161 | /// returning nil from pageViewController:viewControllerAfter: at the end of the pages.
162 | func testPageViewControllerDataSourceNilNextController() {
163 | self.dataSource.numberOfPages = 3
164 | self.dataSource.defaultIndex = .last
165 | self.pageboyViewController.dataSource = self.dataSource
166 |
167 | let viewController = self.dataSource.viewControllers![2]
168 | let nextViewController = self.pageboyViewController.pageViewController(self.pageboyViewController.pageViewController!,
169 | viewControllerAfter: viewController)
170 |
171 | XCTAssertNil(nextViewController,
172 | "pageViewController:viewControllerAfter is returning an incorrect view controller when at the end of pages.")
173 | }
174 |
175 | /// Test that the UIPageViewController data source is
176 | /// returning correct pageViewController:viewControllerBefore: values.
177 | func testPageViewControllerDataSourcePreviousController() {
178 | self.dataSource.numberOfPages = 3
179 | self.dataSource.defaultIndex = .at(index: 1)
180 | self.pageboyViewController.dataSource = self.dataSource
181 |
182 | let viewController = self.dataSource.viewControllers![1]
183 | let previousViewController = self.pageboyViewController.pageViewController(self.pageboyViewController.pageViewController!,
184 | viewControllerBefore: viewController)
185 |
186 | XCTAssert(previousViewController === self.dataSource.viewControllers?[0],
187 | "pageViewController:viewControllerBefore is returning an incorrect view controller")
188 | }
189 |
190 | /// Test that the UIPageViewController data source is
191 | /// returning nil from pageViewController:viewControllerBefore: at the start of the pages.
192 | func testPageViewControllerDataSourceNilPreviousController() {
193 | self.dataSource.numberOfPages = 3
194 | self.pageboyViewController.dataSource = self.dataSource
195 |
196 | let viewController = self.dataSource.viewControllers![0]
197 | let previousViewController = self.pageboyViewController.pageViewController(self.pageboyViewController.pageViewController!,
198 | viewControllerBefore: viewController)
199 |
200 | XCTAssertNil(previousViewController,
201 | "pageViewController:viewControllerBefore is returning an incorrect view controller when at the start of pages.")
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/PageboyInsertionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyInsertionTests.swift
3 | // PageboyTests
4 | //
5 | // Created by Merrick Sapsford on 13/11/2018.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pageboy
11 |
12 | class PageboyInsertionTests: PageboyTests {
13 |
14 | func testInsertPage() {
15 | let initialCount = 4
16 | dataSource.numberOfPages = initialCount
17 | pageboyViewController.dataSource = dataSource
18 |
19 | // Insert
20 | let index = 0
21 | let viewController = dataSource.generateViewControllers(count: 1)[index]
22 | dataSource.viewControllers?.insert(viewController, at: index)
23 | pageboyViewController.insertPage(at: index, then: .doNothing)
24 |
25 | XCTAssert(pageboyViewController.pageCount == initialCount + 1)
26 | }
27 |
28 | func testDeletePage() {
29 | let initialCount = 5
30 | dataSource.numberOfPages = initialCount
31 | pageboyViewController.dataSource = dataSource
32 |
33 | let index = 2
34 | dataSource.viewControllers?.remove(at: index)
35 | pageboyViewController.deletePage(at: index, then: .doNothing)
36 |
37 | XCTAssert(pageboyViewController.pageCount == initialCount - 1)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/PageboyPropertyTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyPropertyTests.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 22/03/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pageboy
11 |
12 | class PageboyPropertyTests: PageboyTests {
13 |
14 | /// Test that currentViewController property returns correct view controller.
15 | func testCorrectCurrentViewControllerReported() {
16 | self.dataSource.numberOfPages = 5
17 | self.pageboyViewController.dataSource = self.dataSource
18 |
19 | performAsyncTest { (completion) in
20 |
21 | self.pageboyViewController.scrollToPage(.next, animated: false) { (newViewController, animated, finished) in
22 | let currentViewController = self.pageboyViewController.currentViewController
23 |
24 | XCTAssertTrue(currentViewController === self.dataSource.viewControllers?[1],
25 | "currentViewController property is incorrect following transitions.")
26 | completion()
27 | }
28 | }
29 | }
30 |
31 | /// Test that setting isScrollEnabled updates internal scroll view correctly.
32 | func testIsScrollEnabledUpdates() {
33 | self.dataSource.numberOfPages = 5
34 | self.pageboyViewController.dataSource = self.dataSource
35 |
36 | self.pageboyViewController.isScrollEnabled = false
37 |
38 | XCTAssertTrue(self.pageboyViewController.pageViewController?.scrollView?.isScrollEnabled == false,
39 | "isScrollEnabled does not update the internal scrollView correctly.")
40 | }
41 |
42 | // func testPageViewControllerOptions() {
43 | // self.dataSource.numberOfPages = 5
44 | // self.pageboyViewController.dataSource = self.dataSource
45 | //
46 | // self.pageboyViewController.interPageSpacing = 12.0
47 | //
48 | // XCTAssert(self.pageboyViewController.pageViewControllerOptions?.count ?? 0 > 0,
49 | // "Custom UIPageViewController options are not being passed.")
50 | // }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/PageboyTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyTests.swift
3 | // PageboyTests
4 | //
5 | // Created by Merrick Sapsford on 04/01/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pageboy
11 |
12 | class PageboyTests: XCTestCase {
13 |
14 | typealias AsyncTest = (@escaping TestCompletion) -> Void
15 | typealias TestCompletion = () -> Void
16 |
17 | var pageboyViewController: TestPageBoyViewController!
18 | var dataSource: TestPageboyDataSource!
19 | var delegate: TestPageboyDelegate!
20 |
21 | private var expectations = [XCTestExpectation]()
22 |
23 | // MARK: Environment
24 |
25 | override func setUp() {
26 | super.setUp()
27 |
28 | pageboyViewController = TestPageBoyViewController()
29 | dataSource = TestPageboyDataSource()
30 | delegate = TestPageboyDelegate()
31 |
32 | pageboyViewController.delegate = delegate
33 |
34 | let bounds = UIScreen.main.bounds
35 | pageboyViewController.view.frame = bounds
36 |
37 | pageboyViewController.loadViewIfNeeded()
38 | }
39 |
40 | // MARK: Tests
41 |
42 | private func testInit() {
43 | XCTAssert(pageboyViewController != nil,
44 | "PageBoyViewController initialization failed")
45 | }
46 |
47 | func performAsyncTest(timeout: TimeInterval = 0.3,
48 | test: AsyncTest) {
49 | let exp = expectation(description: "Async test")
50 | let index = expectations.count
51 | expectations.append(exp)
52 | test { [unowned self] in
53 | exp.fulfill()
54 | self.expectations.remove(at: index)
55 | }
56 | waitForExpectations(timeout: timeout) { (error) in
57 | guard let error = error else {
58 | return
59 | }
60 |
61 | XCTFail("Expectation failed with \(error)")
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/PageboyTransitionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyTransitionTests.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 15/02/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pageboy
11 |
12 | class PageboyTransitionTests: PageboyTests {
13 |
14 | /// Test transition to a valid custom PageIndex non-animated
15 | func testSuccessfulTransitionToCustomIndex() {
16 | self.dataSource.numberOfPages = 5
17 | self.pageboyViewController.dataSource = self.dataSource
18 | let transitionIndex = 2
19 |
20 | performAsyncTest { (completion) in
21 | self.pageboyViewController.scrollToPage(.at(index: transitionIndex),
22 | animated: false)
23 | { (newViewController, animated, finished) in
24 | XCTAssert(self.pageboyViewController.currentIndex == transitionIndex,
25 | "Not transitioning to valid custom index correctly (Non animated).")
26 | completion()
27 | }
28 | }
29 | }
30 |
31 | /// Test attempting transition to an out of bounds custom PageIndex
32 | func testHandlingTransitionToOutOfBoundsCustomIndex() {
33 | self.dataSource.numberOfPages = 5
34 | self.pageboyViewController.dataSource = self.dataSource
35 | let transitionIndex = 6
36 |
37 | self.pageboyViewController.scrollToPage(.at(index: transitionIndex), animated: false)
38 |
39 | XCTAssert(self.pageboyViewController.currentIndex == 0,
40 | "Not handling out of bounds transition index request correctly.")
41 | }
42 |
43 | /// Test transition to a .first PageIndex
44 | func testSuccessfulTransitionToFirstIndex() {
45 | self.dataSource.numberOfPages = 5
46 | self.pageboyViewController.dataSource = self.dataSource
47 |
48 | self.pageboyViewController.scrollToPage(.first, animated: false)
49 |
50 | XCTAssert(self.pageboyViewController.currentIndex == 0,
51 | "Not transitioning to first index correctly.")
52 | }
53 |
54 | /// Test transition to a .last PageIndex
55 | func testSuccessfulTransitionToLastIndex() {
56 | self.dataSource.numberOfPages = 5
57 | self.pageboyViewController.dataSource = self.dataSource
58 |
59 | performAsyncTest { (completion) in
60 | self.pageboyViewController.scrollToPage(.last,
61 | animated: false)
62 | { (newViewController, animated, finished) in
63 | XCTAssert(self.pageboyViewController.currentIndex == 4,
64 | "Not transitioning to last index correctly.")
65 | completion()
66 | }
67 | }
68 | }
69 |
70 | /// Test transition to a .next PageIndex
71 | func testSuccessfulTransitionToNextIndex() {
72 | self.dataSource.numberOfPages = 5
73 | self.pageboyViewController.dataSource = self.dataSource
74 |
75 | performAsyncTest { (completion) in
76 | self.pageboyViewController.scrollToPage(.next,
77 | animated: false)
78 | { (newViewController, animated, finished) in
79 | XCTAssert(self.pageboyViewController.currentIndex == 1,
80 | "Not transitioning to next index correctly.")
81 | completion()
82 | }
83 | }
84 | }
85 |
86 | /// Test transition to a .previous PageIndex
87 | func testSuccessfulTransitionToPreviousIndex() {
88 | self.dataSource.numberOfPages = 5
89 | self.pageboyViewController.dataSource = self.dataSource
90 |
91 | performAsyncTest { (completion) in
92 |
93 | self.pageboyViewController.scrollToPage(.last,
94 | animated: false)
95 | { (newViewController, animated, finished) in
96 | self.pageboyViewController.scrollToPage(.previous,
97 | animated: false)
98 | { (newViewController, animated, finished) in
99 | XCTAssert(self.pageboyViewController.currentIndex == 3,
100 | "Not transitioning to previous index correctly.")
101 | completion()
102 | }
103 | }
104 | }
105 | }
106 |
107 | /// Test partial user interacted transition reports offsets correctly.
108 | func testPartialTransitionOffsetReporting() {
109 | self.dataSource.numberOfPages = 5
110 | self.pageboyViewController.dataSource = self.dataSource
111 |
112 | // simulate scroll
113 | self.simulateScroll(toPosition: 0.5)
114 |
115 | XCTAssert(String(format:"%.1f", self.delegate.lastRecordedPagePosition?.x ?? 0.0) == "0.5" &&
116 | self.pageboyViewController.currentIndex == 0,
117 | "Not reporting partial user interacted transition offset values correctly.")
118 |
119 | }
120 |
121 | /// Test partial user interacted transition reports direction correctly.
122 | func testPartialTransitionDirectionReporting() {
123 | self.dataSource.numberOfPages = 5
124 | self.pageboyViewController.dataSource = self.dataSource
125 |
126 | // simulate scroll
127 | self.simulateScroll(toPosition: 0.5)
128 |
129 | XCTAssert(self.delegate.lastRecordedDirection == .forward && self.pageboyViewController.currentIndex == 0,
130 | "Not reporting partial user interacted transition direction values correctly.")
131 | }
132 |
133 | /// Test animated flags are correct for non-animated transitions.
134 | func testNonAnimatedTransitionAnimatedFlags() {
135 | self.dataSource.numberOfPages = 5
136 | self.pageboyViewController.dataSource = self.dataSource
137 | let transitionIndex = 3
138 |
139 | self.pageboyViewController.scrollToPage(.at(index: transitionIndex), animated: false)
140 | { (newViewController, animated, finished) in
141 |
142 | XCTAssert(self.pageboyViewController.currentIndex == transitionIndex &&
143 | self.delegate.lastWillScrollToPageAnimated == false &&
144 | self.delegate.lastDidScrollToPageAtIndexAnimated == false &&
145 | self.delegate.lastDidScrollToPositionAnimated == false,
146 | "Animated flags for an non animated scrollToPage are incorrect.")
147 | }
148 | }
149 |
150 | /// Test bounces flag is correctly adhered to when set to false.
151 | func testBouncingDisabledTransition() {
152 | self.dataSource.numberOfPages = 2
153 | self.pageboyViewController.dataSource = self.dataSource
154 | self.pageboyViewController.bounces = false
155 |
156 | self.simulateScroll(toPosition: -0.1)
157 |
158 | XCTAssert(self.pageboyViewController.currentPosition!.x == 0.0,
159 | "Bounces flag is not adhered to when setting contentOffset when false.")
160 | }
161 |
162 | // MARK: Utils
163 |
164 | func simulateScroll(toPosition position: CGFloat) {
165 | let targetIndex = Int(position.rounded())
166 |
167 | let boundsWidth = self.pageboyViewController.view.frame.size.width
168 | self.pageboyViewController.expectedTransitionIndex = targetIndex
169 | self.pageboyViewController.pageViewController?.scrollView?.setContentOffset(CGPoint(x: boundsWidth + (boundsWidth * position), y: 0.0),
170 | animated: false)
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/TestComponents/TestPageChildViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestPageChildViewController.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 15/02/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class TestPageChildViewController: UIViewController {
12 |
13 | var index: Int?
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/TestComponents/TestPageboyDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestPageboyDataSource.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 15/02/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Pageboy
12 |
13 | class TestPageboyDataSource: PageboyViewControllerDataSource {
14 |
15 | var numberOfPages: Int? {
16 | didSet {
17 | guard let numberOfPages = numberOfPages else {
18 | self.viewControllers = nil
19 | return
20 | }
21 | self.viewControllers = generateViewControllers(count: numberOfPages)
22 | }
23 | }
24 | var defaultIndex: PageboyViewController.Page?
25 | var viewControllers: [UIViewController]?
26 |
27 | // MARK: PageboyViewControllerDataSource
28 |
29 | func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int {
30 | return viewControllers?.count ?? 0
31 | }
32 |
33 | func viewController(for pageboyViewController: PageboyViewController,
34 | at index: PageboyViewController.PageIndex) -> UIViewController? {
35 | return viewControllers?[index]
36 | }
37 |
38 | func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? {
39 | return defaultIndex
40 | }
41 |
42 | // MARK: Utility
43 |
44 | func generateViewControllers(count: Int) -> [UIViewController] {
45 | var viewControllers = [UIViewController]()
46 |
47 | for index in 0 ..< count {
48 |
49 | let viewController = TestPageChildViewController()
50 | viewController.index = index
51 | viewControllers.append(viewController)
52 | }
53 |
54 | return viewControllers
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/TestComponents/TestPageboyDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestPageboyDelegate.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 15/02/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import Pageboy
12 |
13 | class TestPageboyDelegate: PageboyViewControllerDelegate {
14 |
15 | var lastRecordedPagePosition: CGPoint?
16 | var lastRecordedPageIndex: Int?
17 | var lastRecordedDirection: PageboyViewController.NavigationDirection?
18 |
19 | var lastWillScrollToPageAnimated: Bool?
20 | var lastDidScrollToPositionAnimated: Bool?
21 | var lastDidScrollToPageAtIndexAnimated: Bool?
22 |
23 | var lastDidReloadPageCount: Int?
24 | var lastDidReloadCurrentViewController: UIViewController?
25 | var lastDidReloadCurrentIndex: PageboyViewController.PageIndex?
26 | var reloadCount: Int = 0
27 |
28 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
29 | willScrollToPageAt index: PageboyViewController.PageIndex,
30 | direction: PageboyViewController.NavigationDirection,
31 | animated: Bool) {
32 | lastWillScrollToPageAnimated = animated
33 | }
34 |
35 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
36 | didScrollTo position: CGPoint,
37 | direction: PageboyViewController.NavigationDirection,
38 | animated: Bool) {
39 | lastDidScrollToPositionAnimated = animated
40 | lastRecordedPagePosition = position
41 | lastRecordedDirection = direction
42 | }
43 |
44 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
45 | didScrollToPageAt index: PageboyViewController.PageIndex,
46 | direction: PageboyViewController.NavigationDirection,
47 | animated: Bool) {
48 | lastDidScrollToPageAtIndexAnimated = animated
49 | lastRecordedPageIndex = index
50 | lastRecordedDirection = direction
51 | }
52 |
53 | func pageboyViewController(_ pageboyViewController: PageboyViewController,
54 | didReloadWith currentViewController: UIViewController,
55 | currentPageIndex: PageboyViewController.PageIndex) {
56 | lastDidReloadPageCount = pageboyViewController.pageCount
57 | lastDidReloadCurrentViewController = currentViewController
58 | lastDidReloadCurrentIndex = currentPageIndex
59 |
60 | reloadCount += 1
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Sources/PageboyTests/TestComponents/TestPageboyViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestPageboyViewController.swift
3 | // Pageboy
4 | //
5 | // Created by Merrick Sapsford on 15/02/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Pageboy
11 |
12 | class TestPageBoyViewController: PageboyViewController {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/Shared/GradientBackgroundViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientBackgroundViewController.swift
3 | // Example
4 | //
5 | // Created by Merrick Sapsford on 04/10/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | final class GradientBackgroundViewController: UIViewController {
11 |
12 | // MARK: Properties
13 |
14 | private lazy var gradientView = GradientView(colors: colors)
15 | let child: UIViewController
16 |
17 | let colors: [UIColor]
18 |
19 | // MARK: Init
20 |
21 | init(embedding child: UIViewController, colors: [UIColor]) {
22 | self.child = child
23 | self.colors = colors
24 | super.init(nibName: nil, bundle: nil)
25 | }
26 |
27 | @available(*, unavailable)
28 | required init?(coder: NSCoder) {
29 | fatalError("Not Supported")
30 | }
31 |
32 | // MARK: Lifecycle
33 |
34 | override func viewDidLoad() {
35 | super.viewDidLoad()
36 |
37 | view.addSubview(gradientView)
38 | gradientView.translatesAutoresizingMaskIntoConstraints = false
39 | NSLayoutConstraint.activate([
40 | gradientView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
41 | gradientView.topAnchor.constraint(equalTo: view.topAnchor),
42 | view.trailingAnchor.constraint(equalTo: gradientView.trailingAnchor),
43 | view.bottomAnchor.constraint(equalTo: gradientView.bottomAnchor)
44 | ])
45 |
46 | addChild(child)
47 | view.addSubview(child.view)
48 | child.view.translatesAutoresizingMaskIntoConstraints = false
49 | NSLayoutConstraint.activate([
50 | child.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
51 | child.view.topAnchor.constraint(equalTo: view.topAnchor),
52 | view.trailingAnchor.constraint(equalTo: child.view.trailingAnchor),
53 | view.bottomAnchor.constraint(equalTo: child.view.bottomAnchor)
54 | ])
55 | }
56 | }
57 |
58 | private final class GradientView: UIView {
59 |
60 | // MARK: Properties
61 |
62 | private var gradientLayer: CAGradientLayer? {
63 | if let gradientLayer = self.layer as? CAGradientLayer {
64 | return gradientLayer
65 | }
66 | return nil
67 | }
68 |
69 | var colors: [UIColor]? {
70 | didSet {
71 | var colorRefs = [CGColor]()
72 | for color in colors ?? [] {
73 | colorRefs.append(color.cgColor)
74 | }
75 | gradientLayer?.colors = colorRefs
76 | }
77 | }
78 |
79 | var locations: [Double]? {
80 | didSet {
81 | var locationNumbers = [NSNumber]()
82 | for location in locations ?? [] {
83 | locationNumbers.append(NSNumber(value: location))
84 | }
85 | gradientLayer?.locations = locationNumbers
86 | }
87 | }
88 |
89 | var startPoint: CGPoint = CGPoint(x: 0.5, y: 0.0) {
90 | didSet {
91 | gradientLayer?.startPoint = startPoint
92 | }
93 | }
94 |
95 | var endPoint: CGPoint = CGPoint(x: 0.5, y: 1.0) {
96 | didSet {
97 | gradientLayer?.endPoint = endPoint
98 | }
99 | }
100 |
101 | override class var layerClass: AnyClass {
102 | return CAGradientLayer.self
103 | }
104 |
105 | // MARK: Init
106 |
107 | init(colors: [UIColor]) {
108 | super.init(frame: .zero)
109 | commonInit(colors: colors)
110 | }
111 |
112 | override init(frame: CGRect) {
113 | super.init(frame: frame)
114 | commonInit()
115 | }
116 |
117 | required init?(coder aDecoder: NSCoder) {
118 | super.init(coder: aDecoder)
119 | commonInit()
120 | }
121 |
122 | private func commonInit(colors: [UIColor] = [UIColor.white, UIColor.black]) {
123 | self.colors = colors
124 | gradientLayer?.startPoint = self.startPoint
125 | gradientLayer?.endPoint = self.endPoint
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Sources/Shared/PageboyStatusView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyStatusView.swift
3 | // Examples
4 | //
5 | // Created by Merrick Sapsford on 10/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Pageboy
11 |
12 | class PageboyStatusView: UIView {
13 |
14 | // MARK: Properties
15 |
16 | private let divider = UIView()
17 | private let countLabel = UILabel()
18 | private let positionLabel = UILabel()
19 | private let pageLabel = UILabel()
20 |
21 | override var tintColor: UIColor! {
22 | didSet {
23 | updateForTintColor()
24 | }
25 | }
26 |
27 | // MARK: Init
28 |
29 | override init(frame: CGRect) {
30 | super.init(frame: frame)
31 | commonInit()
32 | }
33 |
34 | required init?(coder: NSCoder) {
35 | super.init(coder: coder)
36 | commonInit()
37 | }
38 |
39 | private func commonInit() {
40 |
41 | let stackView = UIStackView()
42 | stackView.axis = .vertical
43 |
44 | addSubview(divider)
45 | addSubview(stackView)
46 | divider.translatesAutoresizingMaskIntoConstraints = false
47 | stackView.translatesAutoresizingMaskIntoConstraints = false
48 | NSLayoutConstraint.activate([
49 | divider.leadingAnchor.constraint(equalTo: leadingAnchor),
50 | divider.topAnchor.constraint(equalTo: topAnchor),
51 | divider.widthAnchor.constraint(equalToConstant: 1.0),
52 | bottomAnchor.constraint(equalTo: divider.bottomAnchor),
53 | stackView.leadingAnchor.constraint(equalTo: divider.trailingAnchor, constant: 8.0),
54 | stackView.topAnchor.constraint(equalTo: topAnchor),
55 | trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
56 | bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
57 | ])
58 |
59 | stackView.addArrangedSubview(countLabel)
60 | stackView.addArrangedSubview(positionLabel)
61 | stackView.addArrangedSubview(pageLabel)
62 |
63 | updateCount(nil)
64 | updatePosition(nil)
65 | updatePage(nil)
66 |
67 | tintColor = UIColor.white.withAlphaComponent(0.75)
68 |
69 | switch traitCollection.userInterfaceIdiom {
70 | case .tv:
71 | countLabel.font = .systemFont(ofSize: 18)
72 | positionLabel.font = .systemFont(ofSize: 18)
73 | pageLabel.font = .systemFont(ofSize: 18)
74 | default:
75 | countLabel.font = .systemFont(ofSize: 14)
76 | positionLabel.font = .systemFont(ofSize: 14)
77 | pageLabel.font = .systemFont(ofSize: 14)
78 | }
79 |
80 | updateForTintColor()
81 | }
82 |
83 | // MARK: Styling
84 |
85 | private func updateForTintColor() {
86 | divider.backgroundColor = tintColor
87 | countLabel.textColor = tintColor
88 | positionLabel.textColor = tintColor
89 | pageLabel.textColor = tintColor
90 | }
91 |
92 | // MARK: Data
93 |
94 | private func updateCount(_ count: Int?) {
95 | countLabel.text = "Page Count: \(count ?? 0)"
96 | }
97 |
98 | private func updatePosition(_ position: CGFloat?) {
99 | positionLabel.text = "Current Position: \(String(format: "%.3f", position ?? 0.0))"
100 | }
101 |
102 | private func updatePage(_ page: Int?) {
103 | pageLabel.text = "Current Page: \(page ?? 0)"
104 | }
105 | }
106 |
107 | extension PageboyStatusView: PageboyViewControllerDelegate {
108 |
109 | func pageboyViewController(_ pageboyViewController: PageboyViewController, didScrollTo position: CGPoint, direction: PageboyViewController.NavigationDirection, animated: Bool) {
110 | switch pageboyViewController.navigationOrientation {
111 | case .horizontal:
112 | updatePosition(position.x)
113 | case .vertical:
114 | updatePosition(position.y)
115 | @unknown default:
116 | break
117 | }
118 | }
119 |
120 | func pageboyViewController(_ pageboyViewController: PageboyViewController, willScrollToPageAt index: PageboyViewController.PageIndex, direction: PageboyViewController.NavigationDirection, animated: Bool) {
121 |
122 | }
123 |
124 | func pageboyViewController(_ pageboyViewController: PageboyViewController, didScrollToPageAt index: PageboyViewController.PageIndex, direction: PageboyViewController.NavigationDirection, animated: Bool) {
125 | updatePage(index)
126 | }
127 |
128 | func pageboyViewController(_ pageboyViewController: PageboyViewController, didCancelScrollToPageAt index: PageboyViewController.PageIndex, returnToPageAt previousIndex: PageboyViewController.PageIndex) {
129 |
130 | }
131 |
132 | func pageboyViewController(_ pageboyViewController: PageboyViewController, didReloadWith currentViewController: UIViewController, currentPageIndex: PageboyViewController.PageIndex) {
133 | updateCount(pageboyViewController.pageCount)
134 | }
135 | }
136 |
137 | extension PageboyStatusView {
138 |
139 | class func add(to viewController: PageboyViewController) {
140 |
141 | let statusView = PageboyStatusView()
142 | viewController.delegate = statusView
143 |
144 | viewController.view.addSubview(statusView)
145 | statusView.translatesAutoresizingMaskIntoConstraints = false
146 |
147 | NSLayoutConstraint.activate([
148 | statusView.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor, constant: 16.0),
149 | viewController.view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: statusView.bottomAnchor, constant: 8.0)
150 | ])
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/Sources/Shared/UIColor+Pageboy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Pageboy.swift
3 | // Example
4 | //
5 | // Created by Merrick Sapsford on 04/10/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | extension UIColor {
11 |
12 | class var pageboyPrimary: UIColor {
13 | UIColor(red: 0.01, green: 0.00, blue: 0.18, alpha: 1.0)
14 | }
15 |
16 | class var pageboySecondary: UIColor {
17 | UIColor(red: 0.00, green: 0.53, blue: 0.80, alpha: 1.0)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/iOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // iOS
4 | //
5 | // Created by Merrick Sapsford on 04/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Pageboy
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 |
19 | let gradientColors: [UIColor] = [.pageboyPrimary, .pageboySecondary]
20 |
21 | let pageViewController = PageViewController()
22 | PageboyStatusView.add(to: pageViewController)
23 | let navigationController = NavigationController(navigationBarClass: TransparentNavigationBar.self, toolbarClass: nil)
24 | navigationController.viewControllers = [pageViewController]
25 |
26 | window = UIWindow(frame: UIScreen.main.bounds)
27 | window?.rootViewController = GradientBackgroundViewController(embedding: navigationController, colors: gradientColors)
28 | window?.makeKeyAndVisible()
29 |
30 | return true
31 | }
32 | }
33 |
34 | @available(iOS 13, *)
35 | extension AppDelegate {
36 |
37 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
38 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.424",
9 | "green" : "0.090",
10 | "red" : "0.059"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "60x60",
35 | "idiom" : "iphone",
36 | "filename" : "Icon@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "60x60",
41 | "idiom" : "iphone",
42 | "filename" : "Icon@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "idiom" : "ipad",
47 | "size" : "20x20",
48 | "scale" : "1x"
49 | },
50 | {
51 | "idiom" : "ipad",
52 | "size" : "20x20",
53 | "scale" : "2x"
54 | },
55 | {
56 | "idiom" : "ipad",
57 | "size" : "29x29",
58 | "scale" : "1x"
59 | },
60 | {
61 | "idiom" : "ipad",
62 | "size" : "29x29",
63 | "scale" : "2x"
64 | },
65 | {
66 | "idiom" : "ipad",
67 | "size" : "40x40",
68 | "scale" : "1x"
69 | },
70 | {
71 | "idiom" : "ipad",
72 | "size" : "40x40",
73 | "scale" : "2x"
74 | },
75 | {
76 | "size" : "76x76",
77 | "idiom" : "ipad",
78 | "filename" : "IconPad.png",
79 | "scale" : "1x"
80 | },
81 | {
82 | "size" : "76x76",
83 | "idiom" : "ipad",
84 | "filename" : "IconPad@2x.png",
85 | "scale" : "2x"
86 | },
87 | {
88 | "size" : "83.5x83.5",
89 | "idiom" : "ipad",
90 | "filename" : "IconPadPro.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "1024x1024",
95 | "idiom" : "ios-marketing",
96 | "filename" : "Icon.png",
97 | "scale" : "1x"
98 | }
99 | ],
100 | "info" : {
101 | "version" : 1,
102 | "author" : "xcode"
103 | }
104 | }
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/AppIcon.appiconset/Icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/AppIcon.appiconset/Icon@2x.png
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/AppIcon.appiconset/Icon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/AppIcon.appiconset/Icon@3x.png
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/AppIcon.appiconset/IconPad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/AppIcon.appiconset/IconPad.png
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/AppIcon.appiconset/IconPad@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/AppIcon.appiconset/IconPad@2x.png
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/AppIcon.appiconset/IconPadPro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/AppIcon.appiconset/IconPadPro.png
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/bg_logo_launch.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "bg_logo_launch.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "bg_logo_launch@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "bg_logo_launch@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/bg_logo_launch.imageset/bg_logo_launch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/bg_logo_launch.imageset/bg_logo_launch.png
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/bg_logo_launch.imageset/bg_logo_launch@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/bg_logo_launch.imageset/bg_logo_launch@2x.png
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/bg_logo_launch.imageset/bg_logo_launch@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/bg_logo_launch.imageset/bg_logo_launch@3x.png
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/ic_minus.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ic_minus.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true
14 | }
15 | }
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/ic_minus.imageset/ic_minus.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/ic_minus.imageset/ic_minus.pdf
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/ic_plus.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ic_plus.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true
14 | }
15 | }
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/ic_plus.imageset/ic_plus.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/ic_plus.imageset/ic_plus.pdf
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/ic_welcome_icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ic_welcome_icon.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "ic_welcome_icon@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "ic_welcome_icon@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/ic_welcome_icon.imageset/ic_welcome_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/ic_welcome_icon.imageset/ic_welcome_icon.png
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/ic_welcome_icon.imageset/ic_welcome_icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/ic_welcome_icon.imageset/ic_welcome_icon@2x.png
--------------------------------------------------------------------------------
/Sources/iOS/Assets.xcassets/ic_welcome_icon.imageset/ic_welcome_icon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/iOS/Assets.xcassets/ic_welcome_icon.imageset/ic_welcome_icon@3x.png
--------------------------------------------------------------------------------
/Sources/iOS/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Sources/iOS/ChildViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChildViewController.swift
3 | // Example
4 | //
5 | // Created by Merrick Sapsford on 04/10/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class ChildViewController: UIViewController {
11 |
12 | // MARK: Properties
13 |
14 | let page: Int
15 |
16 | override var preferredStatusBarStyle: UIStatusBarStyle {
17 | .lightContent
18 | }
19 |
20 | // MARK: Init
21 |
22 | init(page: Int) {
23 | self.page = page
24 | super.init(nibName: nil, bundle: nil)
25 | }
26 |
27 | @available(*, unavailable)
28 | required init?(coder: NSCoder) {
29 | fatalError("Not supported")
30 | }
31 |
32 | // MARK: Lifecycle
33 |
34 | override func viewDidLoad() {
35 | super.viewDidLoad()
36 |
37 | let label = UILabel()
38 | view.addSubview(label)
39 | label.translatesAutoresizingMaskIntoConstraints = false
40 | NSLayoutConstraint.activate([
41 | label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
42 | label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
43 | ])
44 | label.text = "Page \(page)"
45 | label.font = .systemFont(ofSize: 20.0, weight: .medium)
46 | label.textColor = .lightText
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/iOS/Example iOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Sources/iOS/Extras/GradientBackgroundViewController+Appearance.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientBackgroundViewController+Appearance.swift
3 | // Example iOS
4 | //
5 | // Created by Merrick Sapsford on 10/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension GradientBackgroundViewController {
12 |
13 | override var childForStatusBarStyle: UIViewController? {
14 | child
15 | }
16 |
17 | override var childForStatusBarHidden: UIViewController? {
18 | child
19 | }
20 |
21 | override var childForHomeIndicatorAutoHidden: UIViewController? {
22 | child
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/iOS/Extras/NavigationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationController.swift
3 | // Example iOS
4 | //
5 | // Created by Merrick Sapsford on 10/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class NavigationController: UINavigationController {
12 |
13 | open override var childForStatusBarStyle: UIViewController? {
14 | topViewController
15 | }
16 |
17 | open override var childForStatusBarHidden: UIViewController? {
18 | topViewController
19 | }
20 |
21 | open override var childForHomeIndicatorAutoHidden: UIViewController? {
22 | topViewController
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/iOS/Extras/Pageboy+NavigationNotifications.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Pageboy+NavigationNotifications.swift
3 | // Example iOS
4 | //
5 | // Created by Merrick Sapsford on 11/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Pageboy
11 |
12 | extension Notification.Name {
13 |
14 | static let nextPage = Notification.Name("com.uias.Pageboy.nextPage")
15 | static let previousPage = Notification.Name("com.uias.Pageboy.previousPage")
16 | }
17 |
18 | extension PageboyViewController {
19 |
20 | func registerForNavigationNotifications() {
21 | NotificationCenter.default.addObserver(self, selector: #selector(nextPage(_:)), name: .nextPage, object: nil)
22 | NotificationCenter.default.addObserver(self, selector: #selector(previousPage(_:)), name: .previousPage, object: nil)
23 | }
24 |
25 | @objc private func nextPage(_ sender: Notification) {
26 | scrollToPage(.next, animated: true)
27 | }
28 |
29 | @objc private func previousPage(_ sender: Notification) {
30 | scrollToPage(.previous, animated: true)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/iOS/Extras/PageboyTouchBar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageboyTouchBar.swift
3 | // Example iOS
4 | //
5 | // Created by Merrick Sapsford on 11/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Pageboy
11 |
12 | #if targetEnvironment(macCatalyst)
13 |
14 | extension NSTouchBarItem.Identifier {
15 | static let nextPage = NSTouchBarItem.Identifier("com.uias.Pageboy.nextPage")
16 | static let previousPage = NSTouchBarItem.Identifier("com.uias.Pageboy.previousPage")
17 | }
18 |
19 | extension PageViewController: NSTouchBarDelegate {
20 |
21 | open override func makeTouchBar() -> NSTouchBar? {
22 | let touchBar = NSTouchBar()
23 | touchBar.delegate = self
24 |
25 | touchBar.defaultItemIdentifiers = [
26 | .previousPage,
27 | .nextPage,
28 | .flexibleSpace
29 | ]
30 |
31 | return touchBar
32 | }
33 |
34 | public func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
35 | let touchBarItem: NSTouchBarItem?
36 |
37 | switch identifier {
38 |
39 | case .previousPage:
40 | guard let image = UIImage(systemName: "chevron.left") else {
41 | return nil
42 | }
43 | touchBarItem = NSButtonTouchBarItem(identifier: identifier,
44 | image: image,
45 | target: self,
46 | action: #selector(PageViewController.scrollToPreviousPage(_:)))
47 |
48 | case .nextPage:
49 | guard let image = UIImage(systemName: "chevron.right") else {
50 | return nil
51 | }
52 | touchBarItem = NSButtonTouchBarItem(identifier: identifier,
53 | image: image,
54 | target: self,
55 | action: #selector(PageViewController.scrollToNextPage(_:)))
56 |
57 | default:
58 | touchBarItem = nil
59 | }
60 |
61 | return touchBarItem
62 | }
63 | }
64 |
65 | #endif
66 |
--------------------------------------------------------------------------------
/Sources/iOS/Extras/ToolbarDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToolbarDelegate.swift
3 | // Example iOS
4 | //
5 | // Created by Merrick Sapsford on 11/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ToolbarDelegate: NSObject {
12 |
13 | }
14 |
15 | #if targetEnvironment(macCatalyst)
16 |
17 | extension NSToolbarItem.Identifier {
18 | static let nextPage = NSToolbarItem.Identifier("com.uias.Pageboy.nextPage")
19 | static let previousPage = NSToolbarItem.Identifier("com.uias.Pageboy.previousPage")
20 | }
21 |
22 | extension ToolbarDelegate {
23 |
24 | @objc func nextPage(_ sender: Any?) {
25 | NotificationCenter.default.post(Notification(name: .nextPage))
26 | }
27 |
28 | @objc func previousPage(_ sender: Any?) {
29 | NotificationCenter.default.post(Notification(name: .previousPage))
30 | }
31 | }
32 |
33 | extension ToolbarDelegate: NSToolbarDelegate {
34 |
35 | func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
36 | let identifiers: [NSToolbarItem.Identifier] = [
37 | .previousPage,
38 | .nextPage
39 | ]
40 | return identifiers
41 | }
42 |
43 | func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
44 | return toolbarDefaultItemIdentifiers(toolbar)
45 | }
46 |
47 | func toolbar(_ toolbar: NSToolbar,
48 | itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
49 | willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
50 |
51 | var toolbarItem: NSToolbarItem?
52 |
53 | switch itemIdentifier {
54 |
55 | case .nextPage:
56 | let item = NSToolbarItem(itemIdentifier: itemIdentifier)
57 | item.image = UIImage(systemName: "chevron.right")
58 | item.label = "Next Page"
59 | item.action = #selector(nextPage(_:))
60 | item.target = self
61 | toolbarItem = item
62 |
63 | case .previousPage:
64 | let item = NSToolbarItem(itemIdentifier: itemIdentifier)
65 | item.image = UIImage(systemName: "chevron.left")
66 | item.label = "Previous Page"
67 | item.action = #selector(previousPage(_:))
68 | item.target = self
69 | toolbarItem = item
70 |
71 | default:
72 | toolbarItem = nil
73 | }
74 |
75 | return toolbarItem
76 | }
77 | }
78 | #endif
79 |
--------------------------------------------------------------------------------
/Sources/iOS/Extras/TransparentNavigationBar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TransparentNavigationBar.swift
3 | // Example iOS
4 | //
5 | // Created by Merrick Sapsford on 15/02/2017.
6 | // Copyright © 2022 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class TransparentNavigationBar: UINavigationBar {
12 |
13 | private let separatorView = UIView()
14 |
15 | // MARK: Init
16 |
17 | override init(frame: CGRect) {
18 | super.init(frame: frame)
19 | commonInit()
20 | }
21 |
22 | required init?(coder: NSCoder) {
23 | super.init(coder: coder)
24 | commonInit()
25 | }
26 |
27 | private func commonInit() {
28 | var titleTextAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: UIColor.white]
29 | titleTextAttributes[.font] = UIFont.systemFont(ofSize: 18.0, weight: UIFont.Weight.semibold)
30 | self.titleTextAttributes = titleTextAttributes
31 | self.tintColor = UIColor.white.withAlphaComponent(0.7)
32 |
33 | self.setBackgroundImage(UIImage(), for: .default)
34 | self.shadowImage = UIImage()
35 | self.isTranslucent = true
36 |
37 | separatorView.backgroundColor = UIColor.white.withAlphaComponent(0.5)
38 | self.addSubview(separatorView)
39 | separatorView.frame = CGRect(x: 0.0,
40 | y: self.bounds.size.height - 1.0,
41 | width: self.bounds.size.width, height: 0.5)
42 | }
43 |
44 | // MARK: Lifecycle
45 |
46 | override func layoutSubviews() {
47 | super.layoutSubviews()
48 |
49 | separatorView.frame = CGRect(x: 0.0,
50 | y: self.bounds.size.height - 1.0,
51 | width: self.bounds.size.width, height: 0.5)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Sources/iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIViewControllerBasedStatusBarAppearance
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | Pageboy
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
21 | CFBundleShortVersionString
22 | $(MARKETING_VERSION)
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | UIApplicationSupportsIndirectInputEvents
28 |
29 | UILaunchStoryboardName
30 | LaunchScreen
31 | UIRequiredDeviceCapabilities
32 |
33 | armv7
34 |
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 | UIApplicationSceneManifest
49 |
50 | UIApplicationSupportsMultipleScenes
51 |
52 | UISceneConfigurations
53 |
54 | UIWindowSceneSessionRoleApplication
55 |
56 |
57 | UISceneConfigurationName
58 | Default Configuration
59 | UISceneDelegateClassName
60 | $(PRODUCT_MODULE_NAME).SceneDelegate
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/Sources/iOS/PageViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageViewController.swift
3 | // Example
4 | //
5 | // Created by Merrick Sapsford on 04/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Pageboy
11 |
12 | class PageViewController: PageboyViewController, PageboyViewControllerDataSource {
13 |
14 | // MARK: Properties
15 |
16 | /// View controllers that will be displayed in page view controller.
17 | private lazy var viewControllers: [UIViewController] = {
18 | (1 ... 10).map({ ChildViewController(page: $0) })
19 | }()
20 |
21 | // MARK: Lifecycle
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | // Set PageboyViewControllerDataSource dataSource to configure page view controller.
27 | dataSource = self
28 |
29 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(scrollToNextPage(_:)))
30 | navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Previous", style: .plain, target: self, action: #selector(scrollToPreviousPage(_:)))
31 | }
32 |
33 | // MARK: PageboyViewControllerDataSource
34 |
35 | func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int {
36 | viewControllers.count // How many view controllers to display in the page view controller.
37 | }
38 |
39 | func viewController(for pageboyViewController: PageboyViewController, at index: PageboyViewController.PageIndex) -> UIViewController? {
40 | viewControllers[index] // View controller to display at a specific index for the page view controller.
41 | }
42 |
43 | func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? {
44 | nil // Default page to display in the page view controller (nil equals default/first index).
45 | }
46 |
47 | // MARK: Actions
48 |
49 | @objc func scrollToNextPage(_ sender: UIBarButtonItem) {
50 | scrollToPage(.next, animated: true)
51 | }
52 |
53 | @objc func scrollToPreviousPage(_ sender: UIBarButtonItem) {
54 | scrollToPage(.previous, animated: true)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/iOS/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // Example iOS
4 | //
5 | // Created by Merrick Sapsford on 11/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // swiftlint:disable weak_delegate
12 |
13 | @available(iOS 13, *)
14 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
15 |
16 | var window: UIWindow?
17 | var toolbarDelegate = ToolbarDelegate()
18 |
19 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
20 | guard let windowScene = scene as? UIWindowScene else {
21 | return
22 | }
23 |
24 | let window = UIWindow(windowScene: windowScene)
25 |
26 | let pageViewController = PageViewController()
27 | PageboyStatusView.add(to: pageViewController)
28 |
29 | #if targetEnvironment(macCatalyst)
30 |
31 | let toolbar = NSToolbar(identifier: "main")
32 | toolbar.delegate = toolbarDelegate
33 | toolbar.displayMode = .iconOnly
34 |
35 | if let titlebar = windowScene.titlebar {
36 | titlebar.toolbar = toolbar
37 | if #available(iOS 14.0, *) {
38 | titlebar.toolbarStyle = .automatic
39 | }
40 | }
41 |
42 | pageViewController.registerForNavigationNotifications()
43 | let contentViewController = pageViewController
44 |
45 | #else
46 | let navigationController = NavigationController(navigationBarClass: TransparentNavigationBar.self, toolbarClass: nil)
47 | navigationController.viewControllers = [pageViewController]
48 | let contentViewController = navigationController
49 | #endif
50 |
51 | let gradientColors: [UIColor] = [.pageboyPrimary, .pageboySecondary]
52 | window.rootViewController = GradientBackgroundViewController(embedding: contentViewController, colors: gradientColors)
53 |
54 | self.window = window
55 | window.makeKeyAndVisible()
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Sources/tvOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // tvOS
4 | //
5 | // Created by Merrick Sapsford on 10/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Pageboy
11 |
12 | @main
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 |
19 | let gradientColors: [UIColor] = [.pageboyPrimary, .pageboySecondary]
20 |
21 | let pageViewController = PageViewController()
22 | addStatusView(to: pageViewController)
23 |
24 | window = UIWindow(frame: UIScreen.main.bounds)
25 | window?.rootViewController = GradientBackgroundViewController(embedding: pageViewController, colors: gradientColors)
26 | window?.makeKeyAndVisible()
27 |
28 | return true
29 | }
30 |
31 | func applicationWillResignActive(_ application: UIApplication) {
32 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
33 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
34 | }
35 |
36 | func applicationDidEnterBackground(_ application: UIApplication) {
37 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
38 | }
39 |
40 | func applicationWillEnterForeground(_ application: UIApplication) {
41 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
42 | }
43 |
44 | func applicationDidBecomeActive(_ application: UIApplication) {
45 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
46 | }
47 | }
48 |
49 | extension AppDelegate {
50 |
51 | private func addStatusView(to viewController: PageboyViewController) {
52 |
53 | let statusView = PageboyStatusView()
54 | viewController.delegate = statusView
55 |
56 | viewController.view.addSubview(statusView)
57 | statusView.translatesAutoresizingMaskIntoConstraints = false
58 | NSLayoutConstraint.activate([
59 | statusView.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor, constant: 32.0),
60 | viewController.view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: statusView.bottomAnchor, constant: 8.0)
61 | ])
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Background.png
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Background.png",
5 | "idiom" : "tv"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Foreground.png",
5 | "idiom" : "tv"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Foreground.png
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Middle.png",
5 | "idiom" : "tv"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Middle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Middle.png
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Background.png
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Background@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Background@2x.png
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Background.png",
5 | "idiom" : "tv",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Background@2x.png",
10 | "idiom" : "tv",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Foreground.png",
5 | "idiom" : "tv",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Foreground@2x.png",
10 | "idiom" : "tv",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Foreground.png
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Foreground@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Foreground@2x.png
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Middle.png",
5 | "idiom" : "tv",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Middle@2x.png",
10 | "idiom" : "tv",
11 | "scale" : "2x"
12 | }
13 | ],
14 | "info" : {
15 | "author" : "xcode",
16 | "version" : 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Middle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Middle.png
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Middle@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uias/Pageboy/3f4df3cc962cb61af5d1c0f744db77af5a9ed937/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Middle@2x.png
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "filename" : "App Icon - App Store.imagestack",
5 | "idiom" : "tv",
6 | "role" : "primary-app-icon",
7 | "size" : "1280x768"
8 | },
9 | {
10 | "filename" : "App Icon.imagestack",
11 | "idiom" : "tv",
12 | "role" : "primary-app-icon",
13 | "size" : "400x240"
14 | },
15 | {
16 | "filename" : "Top Shelf Image Wide.imageset",
17 | "idiom" : "tv",
18 | "role" : "top-shelf-image-wide",
19 | "size" : "2320x720"
20 | },
21 | {
22 | "filename" : "Top Shelf Image.imageset",
23 | "idiom" : "tv",
24 | "role" : "top-shelf-image",
25 | "size" : "1920x720"
26 | }
27 | ],
28 | "info" : {
29 | "author" : "xcode",
30 | "version" : 1
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/tvOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/tvOS/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/tvOS/ChildViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChildViewController.swift
3 | // Example tvOS
4 | //
5 | // Created by Merrick Sapsford on 10/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ChildViewController: UIViewController {
12 |
13 | // MARK: Properties
14 |
15 | let page: Int
16 |
17 | // MARK: Init
18 |
19 | init(page: Int) {
20 | self.page = page
21 | super.init(nibName: nil, bundle: nil)
22 | }
23 |
24 | @available(*, unavailable)
25 | required init?(coder: NSCoder) {
26 | fatalError("Not supported")
27 | }
28 |
29 | // MARK: Lifecycle
30 |
31 | override func viewDidLoad() {
32 | super.viewDidLoad()
33 |
34 | let label = UILabel()
35 | view.addSubview(label)
36 | label.translatesAutoresizingMaskIntoConstraints = false
37 | NSLayoutConstraint.activate([
38 | label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
39 | label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
40 | ])
41 | label.text = "Page \(page)"
42 | label.font = .systemFont(ofSize: 32.0, weight: .medium)
43 | label.textColor = .white
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/tvOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Pageboy
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | arm64
30 |
31 | UIUserInterfaceStyle
32 | Automatic
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Sources/tvOS/PageViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PageViewController.swift
3 | // Example tvOS
4 | //
5 | // Created by Merrick Sapsford on 10/10/2020.
6 | // Copyright © 2020 UI At Six. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Pageboy
11 |
12 | class PageViewController: PageboyViewController, PageboyViewControllerDataSource {
13 |
14 | // MARK: Properties
15 |
16 | /// View controllers that will be displayed in page view controller.
17 | private lazy var viewControllers: [UIViewController] = [
18 | ChildViewController(page: 1),
19 | ChildViewController(page: 2),
20 | ChildViewController(page: 3),
21 | ChildViewController(page: 4),
22 | ChildViewController(page: 5)
23 | ]
24 |
25 | // MARK: Lifecycle
26 |
27 | override func viewDidLoad() {
28 | super.viewDidLoad()
29 |
30 | // Set PageboyViewControllerDataSource dataSource to configure page view controller.
31 | dataSource = self
32 | }
33 |
34 | // MARK: PageboyViewControllerDataSource
35 |
36 | func numberOfViewControllers(in pageboyViewController: PageboyViewController) -> Int {
37 | viewControllers.count // How many view controllers to display in the page view controller.
38 | }
39 |
40 | func viewController(for pageboyViewController: PageboyViewController, at index: PageboyViewController.PageIndex) -> UIViewController? {
41 | viewControllers[index] // View controller to display at a specific index for the page view controller.
42 | }
43 |
44 | func defaultPage(for pageboyViewController: PageboyViewController) -> PageboyViewController.Page? {
45 | nil // Default page to display in the page view controller (nil equals default/first index).
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | app_identifier "com.uias.pageboy" # The bundle identifier of your app
2 |
--------------------------------------------------------------------------------
/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 |
2 | fastlane_version "2.26.1"
3 |
4 | default_platform :ios
5 |
6 | platform :ios do
7 |
8 | desc "Run unit tests and check library"
9 | lane :test do
10 | scan(workspace: "Pageboy.xcworkspace", scheme: "Pageboy iOS", clean: true)
11 | pod_lib_lint(allow_warnings: true)
12 | end
13 |
14 | desc "Deploy a new version to CocoaPods and GitHub"
15 | lane :deploy do
16 | test
17 |
18 | podspec = "Pageboy.podspec"
19 | version = version_get_podspec(path: podspec)
20 |
21 | # Push new Github release
22 | github_release = set_github_release(
23 | repository_name: "uias/Pageboy",
24 | api_token: ENV["GITHUB_API_TOKEN"],
25 | name: version,
26 | tag_name: version,
27 | description: "#{version} release.",
28 | commitish: "main"
29 | )
30 |
31 | # Push spec
32 | pod_push(allow_warnings: true, verbose: true)
33 |
34 | slack(
35 | message: "Pageboy v#{version} released!"
36 | )
37 | end
38 | end
--------------------------------------------------------------------------------