├── .codecov.yml
├── .github
└── workflows
│ └── continuous-integration.yml
├── .gitignore
├── .swift-version
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── Assets
├── advanced-example--thumbnail.png
├── advanced-example.png
├── basic-example--thumbnail.png
├── basic-example.png
├── iphone-x-screen-demystified.svg
├── navigation-bar-example--thumbnail.png
├── navigation-bar-example.png
├── notch-example--thumbnail.png
├── notch-example.png
├── safe-area-example--thumbnail.png
├── safe-area-example.png
└── screen.gif
├── CHANGELOG.md
├── Cartfile
├── Example
├── .swiftformat
├── .swiftlint.yml
├── Example.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Example.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Example
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── ExampleApp.swift
│ ├── Helper
│ │ └── StoryboardView.swift
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ └── Scenes
│ │ ├── AdvancedExample
│ │ ├── AdvancedExample.storyboard
│ │ ├── AdvancedExampleTableViewController.swift
│ │ ├── Cells
│ │ │ ├── CustomColorsTableViewCell.swift
│ │ │ ├── CustomSuperviewTableViewCell.swift
│ │ │ └── InterfaceBuilderSetupTableViewCell.swift
│ │ └── Views
│ │ │ └── BorderedButton.swift
│ │ ├── BasicExample
│ │ ├── BasicExample.storyboard
│ │ └── BasicExampleViewController.swift
│ │ ├── EntryPointView.swift
│ │ ├── NavigationBarExample
│ │ ├── NavigationBarExample.storyboard
│ │ └── NavigationBarExampleViewController.swift
│ │ ├── SafeAreaExample
│ │ ├── SafeAreaExample.storyboard
│ │ └── SafeAreaExampleViewController.swift
│ │ └── SwiftUIExample
│ │ └── SwiftUIExampleView.swift
├── Gemfile
├── Gemfile.lock
├── Podfile
├── Podfile.lock
├── Pods
│ ├── AppHost-GradientLoadingBar-Unit-Tests
│ │ ├── AppHost-GradientLoadingBar-Unit-Tests-Info.plist
│ │ ├── LaunchScreen.storyboard
│ │ └── main.m
│ ├── Local Podspecs
│ │ ├── GradientLoadingBar.podspec.json
│ │ └── SwiftConfigurationFiles.podspec.json
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ │ ├── project.pbxproj
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ ├── GradientLoadingBar-Unit-Tests.xcscheme
│ │ │ └── GradientLoadingBar.xcscheme
│ ├── SnapshotTesting
│ │ ├── LICENSE
│ │ ├── README.md
│ │ └── Sources
│ │ │ └── SnapshotTesting
│ │ │ ├── AssertInlineSnapshot.swift
│ │ │ ├── AssertSnapshot.swift
│ │ │ ├── Async.swift
│ │ │ ├── Common
│ │ │ ├── Internal.swift
│ │ │ ├── PlistEncoder.swift
│ │ │ ├── String+SpecialCharacters.swift
│ │ │ ├── View.swift
│ │ │ └── XCTAttachment.swift
│ │ │ ├── Diff.swift
│ │ │ ├── Diffing.swift
│ │ │ ├── Extensions
│ │ │ └── Wait.swift
│ │ │ ├── SnapshotTestCase.swift
│ │ │ ├── Snapshotting.swift
│ │ │ └── Snapshotting
│ │ │ ├── Any.swift
│ │ │ ├── CALayer.swift
│ │ │ ├── CGPath.swift
│ │ │ ├── CaseIterable.swift
│ │ │ ├── Codable.swift
│ │ │ ├── Data.swift
│ │ │ ├── Description.swift
│ │ │ ├── NSBezierPath.swift
│ │ │ ├── NSImage.swift
│ │ │ ├── NSView.swift
│ │ │ ├── NSViewController.swift
│ │ │ ├── SceneKit.swift
│ │ │ ├── SpriteKit.swift
│ │ │ ├── String.swift
│ │ │ ├── SwiftUIView.swift
│ │ │ ├── UIBezierPath.swift
│ │ │ ├── UIImage.swift
│ │ │ ├── UIView.swift
│ │ │ ├── UIViewController.swift
│ │ │ └── URLRequest.swift
│ ├── SwiftConfigurationFiles
│ │ ├── .swiftformat
│ │ ├── .swiftlint.yml
│ │ ├── LICENSE
│ │ └── README.md
│ ├── SwiftFormat
│ │ ├── CommandLineTool
│ │ │ └── swiftformat
│ │ ├── LICENSE.md
│ │ └── README.md
│ ├── SwiftLint
│ │ ├── LICENSE
│ │ └── swiftlint
│ └── Target Support Files
│ │ ├── GradientLoadingBar
│ │ ├── GradientLoadingBar-Info.plist
│ │ ├── GradientLoadingBar-Unit-Tests-Info.plist
│ │ ├── GradientLoadingBar-Unit-Tests-frameworks-input-files.xcfilelist
│ │ ├── GradientLoadingBar-Unit-Tests-frameworks-output-files.xcfilelist
│ │ ├── GradientLoadingBar-Unit-Tests-frameworks.sh
│ │ ├── GradientLoadingBar-Unit-Tests-prefix.pch
│ │ ├── GradientLoadingBar-dummy.m
│ │ ├── GradientLoadingBar-prefix.pch
│ │ ├── GradientLoadingBar-umbrella.h
│ │ ├── GradientLoadingBar.debug.xcconfig
│ │ ├── GradientLoadingBar.modulemap
│ │ ├── GradientLoadingBar.release.xcconfig
│ │ ├── GradientLoadingBar.unit-tests.debug.xcconfig
│ │ └── GradientLoadingBar.unit-tests.release.xcconfig
│ │ ├── Pods-Example
│ │ ├── Pods-Example-Info.plist
│ │ ├── Pods-Example-acknowledgements.markdown
│ │ ├── Pods-Example-acknowledgements.plist
│ │ ├── Pods-Example-dummy.m
│ │ ├── Pods-Example-frameworks-Debug-input-files.xcfilelist
│ │ ├── Pods-Example-frameworks-Debug-output-files.xcfilelist
│ │ ├── Pods-Example-frameworks-Release-input-files.xcfilelist
│ │ ├── Pods-Example-frameworks-Release-output-files.xcfilelist
│ │ ├── Pods-Example-frameworks.sh
│ │ ├── Pods-Example-umbrella.h
│ │ ├── Pods-Example.debug.xcconfig
│ │ ├── Pods-Example.modulemap
│ │ └── Pods-Example.release.xcconfig
│ │ ├── SnapshotTesting
│ │ ├── SnapshotTesting-Info.plist
│ │ ├── SnapshotTesting-dummy.m
│ │ ├── SnapshotTesting-prefix.pch
│ │ ├── SnapshotTesting-umbrella.h
│ │ ├── SnapshotTesting.debug.xcconfig
│ │ ├── SnapshotTesting.modulemap
│ │ └── SnapshotTesting.release.xcconfig
│ │ ├── SwiftConfigurationFiles
│ │ ├── SwiftConfigurationFiles.debug.xcconfig
│ │ └── SwiftConfigurationFiles.release.xcconfig
│ │ ├── SwiftFormat
│ │ ├── SwiftFormat.debug.xcconfig
│ │ └── SwiftFormat.release.xcconfig
│ │ └── SwiftLint
│ │ ├── SwiftLint.debug.xcconfig
│ │ └── SwiftLint.release.xcconfig
└── fastlane
│ ├── Appfile
│ ├── Fastfile
│ └── Pluginfile
├── GradientLoadingBar.podspec
├── GradientLoadingBar
├── Sources
│ ├── Feature
│ │ ├── GradientActivityIndicatorView
│ │ │ ├── GradientActivityIndicatorView+AnimateIsHidden.swift
│ │ │ ├── GradientActivityIndicatorView.swift
│ │ │ └── GradientActivityIndicatorViewModel.swift
│ │ ├── GradientLoadingBar
│ │ │ ├── GradientLoadingBarController.swift
│ │ │ └── GradientLoadingBarViewModel.swift
│ │ ├── GradientLoadingBarView
│ │ │ ├── GradientLoadingBarView+ViewModel.swift
│ │ │ └── GradientLoadingBarView.swift
│ │ └── NotchGradientLoadingBar
│ │ │ ├── NotchGradientLoadingBarController.swift
│ │ │ └── NotchGradientLoadingBarViewModel.swift
│ └── Helper
│ │ └── Constants.swift
└── Tests
│ ├── SnapshotTests
│ ├── GradientActivityIndicatorView
│ │ ├── GradientActivityIndicatorViewTestCase.swift
│ │ └── __Snapshots__
│ │ │ └── GradientActivityIndicatorViewTestCase
│ │ │ ├── test_gradientActivityIndicatorView_shouldContainCorrectCustomColors.1.png
│ │ │ └── test_gradientActivityIndicatorView_shouldContainCorrectDefaultColors.1.png
│ ├── GradientLoadingBar
│ │ ├── GradientLoadingBarControllerTestCase.swift
│ │ └── __Snapshots__
│ │ │ └── GradientLoadingBarControllerTestCase
│ │ │ └── test_gradientLoadingBarController.1.png
│ ├── GradientLoadingBarView
│ │ ├── GradientLoadingBarViewTestCase.swift
│ │ └── __Snapshots__
│ │ │ └── GradientLoadingBarViewTestCase
│ │ │ ├── test_gradientLoadingBarView_shouldContainCorrectCustomColors.1.png
│ │ │ └── test_gradientLoadingBarView_shouldContainCorrectDefaultColors.1.png
│ ├── NotchGradientLoadingBar
│ │ ├── NotchGradientLoadingBarControllerTestCase.swift
│ │ └── __Snapshots__
│ │ │ └── NotchGradientLoadingBarControllerTestCase
│ │ │ └── test_notchGradientLoadingBarController.1.png
│ └── README.md
│ └── UnitTests
│ ├── GradientActivityIndicatorView
│ ├── GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift
│ ├── GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift
│ └── GradientActivityIndicatorViewModelTestCase.swift
│ ├── GradientLoadingBar
│ └── GradientLoadingBarViewModelTestCase.swift
│ ├── GradientLoadingBarView
│ └── GradientLoadingBarView+ViewModelTestCase.swift
│ └── NotchGradientLoadingBar
│ └── NotchGradientLoadingBarViewModelTestCase.swift
├── LICENSE
├── Package.swift
├── _Pods.xcodeproj
└── readme.md
/.codecov.yml:
--------------------------------------------------------------------------------
1 | #
2 | # codecov.yml
3 | # Created by Felix Mau (https://felix.hamburg)
4 | #
5 | # How to:
6 | # - https://github.com/codecov/example-swift
7 | #
8 | # Documentation:
9 | # - https://docs.codecov.io/docs/
10 | #
11 |
12 | ignore:
13 | - "Example" # ignore test coverage of example application
14 |
--------------------------------------------------------------------------------
/.github/workflows/continuous-integration.yml:
--------------------------------------------------------------------------------
1 | #
2 | # continuous-integration.yml
3 | # Created by Felix Mau (https://felix.hamburg)
4 | #
5 | # Based on:
6 | # - https://docs.github.com/en/actions/quickstart
7 | # - https://futurestud.io/tutorials/github-actions-trigger-builds-on-schedule-cron#schedulegithubactionsusingacron
8 | # - https://about.codecov.io/blog/code-coverage-for-ios-development-using-swift-xcode-and-github-actions/
9 | #
10 |
11 | name: Continuous Integration
12 | on:
13 | push:
14 | pull_request:
15 | schedule:
16 | # Run workflow every day at midnight.
17 | #
18 | # - Note: "Scheduled workflows run on the latest commit on the default or base branch."
19 | # https://docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events
20 | - cron: '0 0 * * *'
21 |
22 | # Use latest Xcode version.
23 | #
24 | # Source:
25 | # - https://www.jessesquires.com/blog/2020/01/06/selecting-an-xcode-version-on-github-ci/
26 | # - https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md#xcode
27 | env:
28 | DEVELOPER_DIR: /Applications/Xcode_14.0.app/Contents/Developer
29 |
30 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel.
31 | jobs:
32 | # This workflow starts with a job called "build-and-test".
33 | build-and-test:
34 | name: Build & Test
35 | runs-on: macos-12
36 |
37 | # Steps represent a sequence of tasks that will be executed as part of the job.
38 | steps:
39 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
40 | - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
41 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
42 |
43 | - name: Check out repository code
44 | uses: actions/checkout@v2
45 |
46 | - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
47 | - run: echo "🖥️ The workflow is now ready to test your code on the runner."
48 |
49 | - name: Install dependencies
50 | working-directory: ./Example
51 | run: bundle install
52 |
53 | - name: Execute SwiftFormat and treat any formatting errors as real errors.
54 | working-directory: ./Example
55 | run: bundle exec fastlane format
56 |
57 | - name: Execute SwiftLint and treat any formatting errors as real errors.
58 | working-directory: ./Example
59 | run: bundle exec fastlane lint
60 |
61 | - name: Execute tests.
62 | working-directory: ./Example
63 | run: bundle exec fastlane tests
64 |
65 | - name: Execute validation Carthage support.
66 | working-directory: ./Example
67 | run: bundle exec fastlane verify_carthage
68 |
69 | - name: Execute validation of library.
70 | working-directory: ./Example
71 | run: bundle exec fastlane pod_lint
72 |
73 | - name: Upload coverage to Codecov
74 | uses: codecov/codecov-action@v4
75 |
76 | - run: echo "🍏 This job's status is ${{ job.status }}."
77 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | .build/
7 | *.pbxuser
8 | !default.pbxuser
9 | *.mode1v3
10 | !default.mode1v3
11 | *.mode2v3
12 | !default.mode2v3
13 | *.perspectivev3
14 | !default.perspectivev3
15 | xcuserdata/
16 | *.xccheckout
17 | profile
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 |
23 | # Bundler
24 | .bundle
25 |
26 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
27 | # Carthage/Checkouts
28 |
29 | Carthage/Build
30 |
31 | # We recommend against adding the Pods directory to your .gitignore. However
32 | # you should judge for yourself, the pros and cons are mentioned at:
33 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
34 | #
35 | # Note: if you ignore the Pods directory, make sure to uncomment
36 | # `pod install` in .travis.yml
37 | #
38 |
39 | # Include `Pods` in repo in order to make symlink `Pods.xcodeproj` work (used for Carthage support).
40 | # Pods/
41 |
42 |
43 | #
44 | # Fastlane specific
45 | # Based on: https://docs.fastlane.tools/best-practices/source-control/
46 | #
47 | **/fastlane/report.xml
48 | **/fastlane/Preview.html
49 | **/fastlane/screenshots
50 | **/fastlane/test_output
51 | **/fastlane/README.md
52 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.5
2 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Assets/advanced-example--thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Assets/advanced-example--thumbnail.png
--------------------------------------------------------------------------------
/Assets/advanced-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Assets/advanced-example.png
--------------------------------------------------------------------------------
/Assets/basic-example--thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Assets/basic-example--thumbnail.png
--------------------------------------------------------------------------------
/Assets/basic-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Assets/basic-example.png
--------------------------------------------------------------------------------
/Assets/navigation-bar-example--thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Assets/navigation-bar-example--thumbnail.png
--------------------------------------------------------------------------------
/Assets/navigation-bar-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Assets/navigation-bar-example.png
--------------------------------------------------------------------------------
/Assets/notch-example--thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Assets/notch-example--thumbnail.png
--------------------------------------------------------------------------------
/Assets/notch-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Assets/notch-example.png
--------------------------------------------------------------------------------
/Assets/safe-area-example--thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Assets/safe-area-example--thumbnail.png
--------------------------------------------------------------------------------
/Assets/safe-area-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Assets/safe-area-example.png
--------------------------------------------------------------------------------
/Assets/screen.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Assets/screen.gif
--------------------------------------------------------------------------------
/Cartfile:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Cartfile
--------------------------------------------------------------------------------
/Example/.swiftformat:
--------------------------------------------------------------------------------
1 | #
2 | # .swiftformat
3 | # Created by Felix Mau (https://felix.hamburg)
4 | #
5 |
6 | #
7 | # "Global" formatting options
8 | #
9 | # "Global" formatting options will be applied from the `SwiftConfigurationFiles` repository.
10 | # See https://github.com/fxm90/SwiftConfigurationFiles for further details.
11 | #
12 |
13 | #
14 | # File options
15 | #
16 | # Note: Excluded paths are relative to the SwiftFormat configuration file.
17 | # Therefore we can't specify them in the configuration file from the `SwiftConfigurationFiles` repository.
18 | #
19 | --exclude Pods
--------------------------------------------------------------------------------
/Example/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | #
2 | # .swiftlint.yml
3 | # Created by Felix Mau (https://felix.hamburg)
4 | #
5 |
6 | opt_in_rules:
7 | - file_header
8 |
9 | file_header:
10 | required_pattern: |
11 | \/\/
12 | \/\/ SWIFTLINT_CURRENT_FILENAME
13 | \/\/ (GradientLoadingBar|Example|ExampleTests|ExampleSnapshotTests)
14 | \/\/
15 | \/\/ Created by .*? on \d{2}\.\d{2}\.\d{2}\.
16 | \/\/ Copyright © \d{4} .*?\. All rights reserved\.
17 | \/\/
18 |
19 | included:
20 | - ./
21 | - ../GradientLoadingBar
22 |
23 | excluded:
24 | - Pods
25 |
26 | parent_config: Pods/SwiftConfigurationFiles/.swiftlint.yml
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/Example.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/Example/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 |
--------------------------------------------------------------------------------
/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Example/Example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Example/ExampleApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleApp.swift
3 | // Example
4 | //
5 | // Created by Felix Mau on 07.03.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | @main
12 | struct ExampleApp: App {
13 | var body: some Scene {
14 | WindowGroup {
15 | EntryPointView()
16 | .onAppear {
17 | // Use iOS 13 navigation bar appearance.
18 | //
19 | // While the entry point scene looks better with an opaque navigation background, the child scenes look
20 | // better with the default navigation background. As we can't update the appearance per scene we fallback
21 | // to an appearance with the default background here.
22 | //
23 | // Based on: https://sarunw.com/posts/uinavigationbar-changes-in-ios13/
24 | let appearance = UINavigationBarAppearance()
25 | appearance.configureWithDefaultBackground()
26 |
27 | UINavigationBar.appearance().scrollEdgeAppearance = appearance
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Example/Example/Helper/StoryboardView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoryboardView.swift
3 | // Example
4 | //
5 | // Created by Felix Mau on 02.02.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct StoryboardView: UIViewControllerRepresentable {
12 |
13 | // MARK: - Public properties
14 |
15 | let name: String
16 |
17 | // MARK: - Public methods
18 |
19 | func makeUIViewController(context _: Context) -> UIViewController {
20 | let storyboard = UIStoryboard(name: name, bundle: nil)
21 | guard let viewController = storyboard.instantiateInitialViewController() else {
22 | fatalError("⚠️ – Could not instantiate initial view controller for storyboard with name `\(name)`.")
23 | }
24 |
25 | return viewController
26 | }
27 |
28 | func updateUIViewController(_: UIViewController, context _: Context) {}
29 | }
30 |
--------------------------------------------------------------------------------
/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Example/Scenes/AdvancedExample/AdvancedExampleTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdvancedExampleTableViewController.swift
3 | // Example
4 | //
5 | // Created by Felix Mau on 12.04.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import GradientLoadingBar
10 | import SwiftUI
11 | import UIKit
12 |
13 | final class AdvancedExampleTableViewController: UITableViewController {
14 |
15 | // MARK: - Config
16 |
17 | private enum Config {
18 | /// The custom gradient colors we use.
19 | /// Source: https://color.adobe.com/Pink-Flamingo-color-theme-10343714/
20 | static let gradientColors = [
21 | #colorLiteral(red: 0.9490196078, green: 0.3215686275, blue: 0.431372549, alpha: 1), #colorLiteral(red: 0.9450980392, green: 0.4784313725, blue: 0.5921568627, alpha: 1), #colorLiteral(red: 0.9529411765, green: 0.737254902, blue: 0.7843137255, alpha: 1), #colorLiteral(red: 0.4274509804, green: 0.8666666667, blue: 0.9490196078, alpha: 1), #colorLiteral(red: 0.7568627451, green: 0.9411764706, blue: 0.9568627451, alpha: 1),
22 | ]
23 |
24 | static let firstSectionHeaderHeight: CGFloat = 48
25 | static let defaultSectionHeaderHeight: CGFloat = 32
26 | }
27 |
28 | // MARK: - Types
29 |
30 | private enum Section: CaseIterable {
31 | case customColors
32 | case interfaceBuilderSetup
33 | case customSuperview
34 |
35 | var cellIdentifier: String {
36 | switch self {
37 | case .customColors:
38 | return CustomColorsTableViewCell.reuseIdentifier
39 |
40 | case .interfaceBuilderSetup:
41 | return InterfaceBuilderSetupTableViewCell.reuseIdentifier
42 |
43 | case .customSuperview:
44 | return CustomSuperviewTableViewCell.reuseIdentifier
45 | }
46 | }
47 |
48 | var sectionTitle: String {
49 | switch self {
50 | case .customColors:
51 | return "Custom Colors"
52 |
53 | case .interfaceBuilderSetup:
54 | return "Interface Builder Setup"
55 |
56 | case .customSuperview:
57 | return "Custom Superview"
58 | }
59 | }
60 |
61 | var sectionHeaderHeight: CGFloat {
62 | switch self {
63 | case .customColors:
64 | return Config.firstSectionHeaderHeight
65 |
66 | case .interfaceBuilderSetup, .customSuperview:
67 | return Config.defaultSectionHeaderHeight
68 | }
69 | }
70 | }
71 |
72 | // MARK: - Private properties
73 |
74 | private let gradientLoadingBar = GradientLoadingBar()
75 |
76 | // MARK: - Public methods
77 |
78 | override func viewDidLoad() {
79 | super.viewDidLoad()
80 |
81 | view.backgroundColor = .systemGroupedBackground
82 |
83 | gradientLoadingBar.gradientColors = Config.gradientColors
84 | }
85 |
86 | // MARK: - Table view data source
87 |
88 | override func numberOfSections(in _: UITableView) -> Int {
89 | Section.allCases.count
90 | }
91 |
92 | override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
93 | 1
94 | }
95 |
96 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
97 | let section = Section.allCases[indexPath.section]
98 | switch section {
99 | case .customColors:
100 | guard let cell = tableView.dequeueReusableCell(withIdentifier: section.cellIdentifier, for: indexPath) as? CustomColorsTableViewCell else {
101 | fatalError("⚠️ – Invalid table view setup. Expected to have `CustomColorsTableViewCell` at this point!")
102 | }
103 |
104 | cell.tapHandler = { [weak self] in
105 | if self?.gradientLoadingBar.isHidden == true {
106 | self?.gradientLoadingBar.fadeIn()
107 | } else {
108 | self?.gradientLoadingBar.fadeOut()
109 | }
110 | }
111 |
112 | return cell
113 |
114 | case .interfaceBuilderSetup, .customSuperview:
115 | return tableView.dequeueReusableCell(withIdentifier: section.cellIdentifier, for: indexPath)
116 | }
117 | }
118 |
119 | override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
120 | Section.allCases[section].sectionTitle
121 | }
122 |
123 | override func tableView(_: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
124 | Section.allCases[section].sectionHeaderHeight
125 | }
126 | }
127 |
128 | // MARK: - Helper
129 |
130 | struct AdvancedExampleView: View {
131 | var body: some View {
132 | StoryboardView(name: "AdvancedExample")
133 | .navigationTitle("🚀 Advanced Example")
134 | .edgesIgnoringSafeArea(.bottom)
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/Example/Example/Scenes/AdvancedExample/Cells/CustomColorsTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomColorsTableViewCell.swift
3 | // Example
4 | //
5 | // Created by Felix Mau on 15.04.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class CustomColorsTableViewCell: UITableViewCell {
12 |
13 | // MARK: - Public properties
14 |
15 | static let reuseIdentifier = "CustomColorsTableViewCell"
16 |
17 | var tapHandler: (() -> Void)?
18 |
19 | // MARK: - Private methods
20 |
21 | @IBAction private func customColorsButtonTouchUpInside(_: Any) {
22 | tapHandler?()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Example/Example/Scenes/AdvancedExample/Cells/CustomSuperviewTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomSuperviewTableViewCell.swift
3 | // Example
4 | //
5 | // Created by Felix Mau on 15.04.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import GradientLoadingBar
10 | import UIKit
11 |
12 | final class CustomSuperviewTableViewCell: UITableViewCell {
13 |
14 | // MARK: - Config
15 |
16 | private enum Config {
17 | static let height: CGFloat = 3
18 | }
19 |
20 | // MARK: - Outlets
21 |
22 | @IBOutlet private var toggleButton: UIButton!
23 |
24 | // MARK: - Public properties
25 |
26 | static let reuseIdentifier = "CustomSuperviewViewCell"
27 |
28 | // MARK: - Private properties
29 |
30 | private let gradientActivityIndicatorView = GradientActivityIndicatorView()
31 |
32 | // MARK: - Public methods
33 |
34 | override func awakeFromNib() {
35 | super.awakeFromNib()
36 |
37 | setupGradientActivityIndicatorView()
38 | }
39 |
40 | // MARK: - Private methods
41 |
42 | private func setupGradientActivityIndicatorView() {
43 | gradientActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
44 | toggleButton.addSubview(gradientActivityIndicatorView)
45 |
46 | NSLayoutConstraint.activate([
47 | gradientActivityIndicatorView.leadingAnchor.constraint(equalTo: toggleButton.leadingAnchor),
48 | gradientActivityIndicatorView.trailingAnchor.constraint(equalTo: toggleButton.trailingAnchor),
49 |
50 | gradientActivityIndicatorView.bottomAnchor.constraint(equalTo: toggleButton.bottomAnchor),
51 | gradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: Config.height),
52 | ])
53 | }
54 |
55 | @IBAction private func toggleButtonTouchUpInside(_: Any) {
56 | if gradientActivityIndicatorView.isHidden {
57 | gradientActivityIndicatorView.fadeIn()
58 | } else {
59 | gradientActivityIndicatorView.fadeOut()
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Example/Example/Scenes/AdvancedExample/Cells/InterfaceBuilderSetupTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InterfaceBuilderSetupTableViewCell.swift
3 | // Example
4 | //
5 | // Created by Felix Mau on 15.04.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import GradientLoadingBar
10 | import UIKit
11 |
12 | final class InterfaceBuilderSetupTableViewCell: UITableViewCell {
13 |
14 | // MARK: - Config
15 |
16 | private enum Config {
17 | static let animationDuration: TimeInterval = 0.5
18 | }
19 |
20 | // MARK: - Outlets
21 |
22 | @IBOutlet private var gradientActivityIndicator: GradientActivityIndicatorView!
23 |
24 | // MARK: - Public properties
25 |
26 | static let reuseIdentifier = "InterfaceBuilderSetupTableViewCell"
27 |
28 | // MARK: - Private methods
29 |
30 | @IBAction private func toggleIBSetupButtonTouchUpInside(_: Any) {
31 | // We explicitly "only" reduce the alpha here, as calling `fadeIn()` / `fadeOut()` would update the `isHidden`
32 | // flag accordingly. This would then lead to a height-update of the parent stack view.
33 | UIView.animate(withDuration: Config.animationDuration) {
34 | if self.gradientActivityIndicator.alpha > 0 {
35 | self.gradientActivityIndicator.alpha = 0
36 | } else {
37 | self.gradientActivityIndicator.alpha = 1
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Example/Example/Scenes/AdvancedExample/Views/BorderedButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BorderedButton.swift
3 | // Example
4 | //
5 | // Created by Felix Mau on 15.04.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @IBDesignable
12 | final class BorderedButton: UIButton {
13 |
14 | // MARK: - Config
15 |
16 | private enum Config {
17 | static let borderColor = #colorLiteral(red: 0.2862745098, green: 0.5647058824, blue: 0.8862745098, alpha: 1)
18 | static let borderWidth: CGFloat = 1
19 | static let cornerRadius: CGFloat = 4
20 | }
21 |
22 | // MARK: - Instance Lifecycle
23 |
24 | override init(frame: CGRect) {
25 | super.init(frame: frame)
26 |
27 | commonInit()
28 | }
29 |
30 | required init?(coder aDecoder: NSCoder) {
31 | super.init(coder: aDecoder)
32 |
33 | commonInit()
34 | }
35 |
36 | // MARK: - Private methods
37 |
38 | private func commonInit() {
39 | backgroundColor = .clear
40 | tintColor = Config.borderColor
41 |
42 | layer.borderColor = Config.borderColor.cgColor
43 | layer.borderWidth = Config.borderWidth
44 | layer.cornerRadius = Config.cornerRadius
45 | layer.masksToBounds = true
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Example/Example/Scenes/BasicExample/BasicExampleViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BasicExampleViewController.swift
3 | // Example
4 | //
5 | // Created by Felix Mau on 07.03.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import GradientLoadingBar
10 | import SwiftUI
11 | import UIKit
12 |
13 | final class BasicExampleViewController: UIViewController {
14 |
15 | // MARK: - Public methods
16 |
17 | override func viewWillDisappear(_ animated: Bool) {
18 | super.viewWillDisappear(animated)
19 |
20 | // Reset any possible visible loading bar.
21 | GradientLoadingBar.shared.fadeOut()
22 | }
23 |
24 | // MARK: - Private methods
25 |
26 | @IBAction private func showButtonTouchUpInside(_: Any) {
27 | GradientLoadingBar.shared.fadeIn()
28 | }
29 |
30 | @IBAction private func hideButtonTouchUpInside(_: Any) {
31 | GradientLoadingBar.shared.fadeOut()
32 | }
33 | }
34 |
35 | // MARK: - Helper
36 |
37 | struct BasicExampleView: View {
38 | var body: some View {
39 | StoryboardView(name: "BasicExample")
40 | .navigationTitle("🏡 Basic Example")
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Example/Example/Scenes/EntryPointView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EntryPointView.swift
3 | // Example
4 | //
5 | // Created by Felix Mau on 07.03.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct EntryPointView: View {
12 |
13 | // MARK: - Private properties
14 |
15 | @State
16 | private var isNavigationBarExampleVisible = false
17 |
18 | // MARK: - Render
19 |
20 | var body: some View {
21 | NavigationView {
22 | List {
23 | Section(header: Text("UIKit Examples")) {
24 | NavigationLink(destination: BasicExampleView()) {
25 | TitleSubtitleView(title: "🏡 Basic Example",
26 | subtitle: "Basic usage and setup.")
27 | }
28 |
29 | NavigationLink(destination: SafeAreaExampleView()) {
30 | TitleSubtitleView(title: "📲 Safe Area Example",
31 | subtitle: "Loading bar ignoring the safe area.")
32 | }
33 |
34 | NavigationLink(destination: AdvancedExampleView()) {
35 | TitleSubtitleView(title: "🚀 Advanced Example",
36 | subtitle: "How to apply e.g. custom colors.")
37 | }
38 |
39 | Button {
40 | isNavigationBarExampleVisible = true
41 | } label: {
42 | TitleSubtitleView(title: "🧭 Navigation Bar Example",
43 | subtitle: "Attach the loading bar to a navigation bar.")
44 | // Make entire row tappable and not just the text.
45 | .frame(maxWidth: .infinity, alignment: .leading)
46 | .contentShape(Rectangle())
47 | }
48 | .buttonStyle(PlainButtonStyle())
49 | }
50 |
51 | Section(header: Text("SwiftUI Examples")) {
52 | NavigationLink(destination: SwiftUIExampleView()) {
53 | TitleSubtitleView(title: "🎨 GradientLoadingBarView Example",
54 | subtitle: "How to use the SwiftUI view.")
55 | }
56 | }
57 | }
58 | // We present the `NavigationBarExampleView` as a sheet using it's own navigation controller.
59 | // The `NavigationView` passed from SwiftUI isn't accessible from a `UIViewController` in a way, that we can add subviews to it.
60 | .sheet(isPresented: $isNavigationBarExampleVisible) {
61 | NavigationBarExampleView()
62 | }
63 | // Unfortunately setting the title here results in constraint warnings.
64 | // I couldn't find a possible fix yet, even `.navigationViewStyle(.stack)` doesn't seem to work.
65 | // https://stackoverflow.com/q/65316497
66 | .navigationTitle("GradientLoadingBar")
67 | .navigationBarTitleDisplayMode(.large)
68 | }
69 | }
70 | }
71 |
72 | // MARK: - Subviews
73 |
74 | private struct TitleSubtitleView: View {
75 | let title: String
76 | let subtitle: String
77 |
78 | var body: some View {
79 | VStack(alignment: .leading, spacing: 8) {
80 | Text(title)
81 | .font(.headline)
82 | Text(subtitle)
83 | .font(.subheadline)
84 | }
85 | .padding(.vertical, 8)
86 | }
87 | }
88 |
89 | // MARK: - Preview
90 |
91 | struct EntryPointView_Previews: PreviewProvider {
92 | static var previews: some View {
93 | EntryPointView()
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Example/Example/Scenes/NavigationBarExample/NavigationBarExampleViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationBarExampleViewController.swift
3 | // Example
4 | //
5 | // Created by Felix Mau on 07.03.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import GradientLoadingBar
10 | import SwiftUI
11 | import UIKit
12 |
13 | final class NavigationBarExampleViewController: UIViewController {
14 |
15 | // MARK: - Config
16 |
17 | private enum Config {
18 | /// The programatically applied height of the `GradientActivityIndicatorView`.
19 | static let height: CGFloat = 3
20 | }
21 |
22 | // MARK: - Private properties
23 |
24 | private let gradientProgressIndicatorView = GradientActivityIndicatorView()
25 |
26 | // MARK: - Public methods
27 |
28 | override func viewDidLoad() {
29 | super.viewDidLoad()
30 |
31 | let barButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonTapped))
32 | navigationItem.rightBarButtonItem = barButtonItem
33 |
34 | setupGradientProgressIndicatorView()
35 | }
36 |
37 | override func viewWillDisappear(_ animated: Bool) {
38 | super.viewWillDisappear(animated)
39 |
40 | // Reset any possible visible loading bar.
41 | gradientProgressIndicatorView.fadeOut()
42 | }
43 |
44 | // MARK: - Private methods
45 |
46 | private func setupGradientProgressIndicatorView() {
47 | guard let navigationBar = navigationController?.navigationBar else { return }
48 |
49 | gradientProgressIndicatorView.translatesAutoresizingMaskIntoConstraints = false
50 | navigationBar.addSubview(gradientProgressIndicatorView)
51 |
52 | NSLayoutConstraint.activate([
53 | gradientProgressIndicatorView.leadingAnchor.constraint(equalTo: navigationBar.leadingAnchor),
54 | gradientProgressIndicatorView.trailingAnchor.constraint(equalTo: navigationBar.trailingAnchor),
55 |
56 | gradientProgressIndicatorView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor),
57 | gradientProgressIndicatorView.heightAnchor.constraint(equalToConstant: Config.height),
58 | ])
59 | }
60 |
61 | @IBAction private func doneButtonTapped(_: Any) {
62 | dismiss(animated: true)
63 | }
64 |
65 | @IBAction private func showButtonTouchUpInside(_: Any) {
66 | gradientProgressIndicatorView.fadeIn()
67 | }
68 |
69 | @IBAction private func hideButtonTouchUpInside(_: Any) {
70 | gradientProgressIndicatorView.fadeOut()
71 | }
72 | }
73 |
74 | // MARK: - Helper
75 |
76 | struct NavigationBarExampleView: View {
77 | var body: some View {
78 | StoryboardView(name: "NavigationBarExample")
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Example/Example/Scenes/SafeAreaExample/SafeAreaExampleViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SafeAreaExampleViewController.swift
3 | // Example
4 | //
5 | // Created by Felix Mau on 07.03.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import GradientLoadingBar
10 | import SwiftUI
11 | import UIKit
12 |
13 | final class SafeAreaExampleViewController: UIViewController {
14 |
15 | // MARK: - Private properties
16 |
17 | private let gradientLoadingBar = GradientLoadingBar(isRelativeToSafeArea: false)
18 | private let notchGradientLoadingBar = NotchGradientLoadingBar(isRelativeToSafeArea: false)
19 |
20 | // MARK: - Public methods
21 |
22 | override func viewWillDisappear(_ animated: Bool) {
23 | super.viewWillDisappear(animated)
24 |
25 | // Reset any possible visible loading bar.
26 | gradientLoadingBar.fadeOut()
27 | notchGradientLoadingBar.fadeOut()
28 | }
29 |
30 | // MARK: - Private methods
31 |
32 | @IBAction private func showBasicBarButtonTouchUpInside(_: Any) {
33 | gradientLoadingBar.fadeIn()
34 | }
35 |
36 | @IBAction private func hideBasicBarButtonTouchUpInside(_: Any) {
37 | gradientLoadingBar.fadeOut()
38 | }
39 |
40 | @IBAction private func showNotchBarButtonTouchUpInside(_: Any) {
41 | notchGradientLoadingBar.fadeIn()
42 | }
43 |
44 | @IBAction private func hideNotchBarButtonTouchUpInside(_: Any) {
45 | notchGradientLoadingBar.fadeOut()
46 | }
47 | }
48 |
49 | // MARK: - Helper
50 |
51 | struct SafeAreaExampleView: View {
52 | var body: some View {
53 | StoryboardView(name: "SafeAreaExample")
54 | .navigationTitle("📲 Safe Area Example")
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Example/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "cocoapods"
4 | gem "fastlane"
5 |
6 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
7 | eval_gemfile(plugins_path) if File.exist?(plugins_path)
8 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | # Fixes warning `Your project does not explicitly specify the CocoaPods master specs repo`.
2 | source 'https://github.com/CocoaPods/Specs.git'
3 |
4 | platform :ios, '13.0'
5 | use_frameworks!
6 |
7 | target 'Example' do
8 | pod 'GradientLoadingBar', :path => '../', :testspecs => ['Tests']
9 |
10 | # Development pods.
11 | pod 'SwiftFormat/CLI', '~> 0.41'
12 | pod 'SwiftLint', '~> 0.42'
13 | pod 'SwiftConfigurationFiles', :git => 'https://github.com/fxm90/SwiftConfigurationFiles.git'
14 |
15 | post_install do |installer|
16 | installer.pods_project.targets.each do |target|
17 | target.build_configurations.each do |config|
18 | if target.name == 'GradientLoadingBar'
19 | # Explicitly set the iOS deployment target for `GradientLoadingBar` in our pods project, as this will be read by Carthage.
20 | #
21 | # - Note: This value has to match `s.ios.deployment_target` from the `.podspec` file!!
22 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
23 | else
24 | # Remove the deployment target from all other pods in our project and let them inherit
25 | # the project/workspace deployment target that has been specified at the top of the Podfile.
26 | # Source: https://stackoverflow.com/a/63489366
27 | config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET'
28 | end
29 | end
30 | end
31 |
32 | #
33 | # Fix issues with interface builder and CocoaPods.
34 | #
35 | # Source: https://github.com/CocoaPods/CocoaPods/issues/7606#issuecomment-484294739
36 | #
37 | installer.pods_project.build_configurations.each do |config|
38 | next unless config.name == 'Debug'
39 |
40 | config.build_settings['LD_RUNPATH_SEARCH_PATHS'] = [
41 | '$(FRAMEWORK_SEARCH_PATHS)'
42 | ]
43 | end
44 | end
45 | end
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - GradientLoadingBar (3.0.0)
3 | - GradientLoadingBar/Tests (3.0.0):
4 | - SnapshotTesting (~> 1.9)
5 | - SnapshotTesting (1.9.0)
6 | - SwiftConfigurationFiles (0.0.1)
7 | - SwiftFormat/CLI (0.50.1)
8 | - SwiftLint (0.49.1)
9 |
10 | DEPENDENCIES:
11 | - GradientLoadingBar (from `../`)
12 | - GradientLoadingBar/Tests (from `../`)
13 | - SwiftConfigurationFiles (from `https://github.com/fxm90/SwiftConfigurationFiles.git`)
14 | - SwiftFormat/CLI (~> 0.41)
15 | - SwiftLint (~> 0.42)
16 |
17 | SPEC REPOS:
18 | https://github.com/CocoaPods/Specs.git:
19 | - SnapshotTesting
20 | - SwiftFormat
21 | - SwiftLint
22 |
23 | EXTERNAL SOURCES:
24 | GradientLoadingBar:
25 | :path: "../"
26 | SwiftConfigurationFiles:
27 | :git: https://github.com/fxm90/SwiftConfigurationFiles.git
28 |
29 | CHECKOUT OPTIONS:
30 | SwiftConfigurationFiles:
31 | :commit: efe01ba39069e8957fb89217b8c7fed298254691
32 | :git: https://github.com/fxm90/SwiftConfigurationFiles.git
33 |
34 | SPEC CHECKSUMS:
35 | GradientLoadingBar: 3c4246535efdedaf9b471c05caabfdb4b87b40a2
36 | SnapshotTesting: 6141c48b6aa76ead61431ca665c14ab9a066c53b
37 | SwiftConfigurationFiles: 1cf2228a911ebed9f42f8dec077bb634f04ca6c8
38 | SwiftFormat: e73212c71908404e333da34e303772b9e516ac9b
39 | SwiftLint: 32ee33ded0636d0905ef6911b2b67bbaeeedafa5
40 |
41 | PODFILE CHECKSUM: ea7965c23e5364bcd5cd7d3693d685af73a3e31d
42 |
43 | COCOAPODS: 1.11.3
44 |
--------------------------------------------------------------------------------
/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/AppHost-GradientLoadingBar-Unit-Tests-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 | APPL
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSAppTransportSecurity
24 |
25 | NSAllowsArbitraryLoads
26 |
27 |
28 | NSPrincipalClass
29 |
30 | UILaunchStoryboardName
31 | LaunchScreen
32 | UISupportedInterfaceOrientations
33 |
34 | UIInterfaceOrientationPortrait
35 | UIInterfaceOrientationLandscapeLeft
36 | UIInterfaceOrientationLandscapeRight
37 |
38 | UISupportedInterfaceOrientations~ipad
39 |
40 | UIInterfaceOrientationPortrait
41 | UIInterfaceOrientationPortraitUpsideDown
42 | UIInterfaceOrientationLandscapeLeft
43 | UIInterfaceOrientationLandscapeRight
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/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 |
--------------------------------------------------------------------------------
/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface CPTestAppHostAppDelegate : UIResponder
5 |
6 | @property (nonatomic, strong) UIWindow *window;
7 |
8 | @end
9 |
10 | @implementation CPTestAppHostAppDelegate
11 |
12 | - (BOOL)application:(UIApplication *)__unused application didFinishLaunchingWithOptions:(NSDictionary *)__unused launchOptions
13 | {
14 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
15 | self.window.rootViewController = [UIViewController new];
16 |
17 | [self.window makeKeyAndVisible];
18 |
19 | return YES;
20 | }
21 |
22 | @end
23 |
24 | int main(int argc, char *argv[])
25 | {
26 | @autoreleasepool
27 | {
28 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([CPTestAppHostAppDelegate class]));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/GradientLoadingBar.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "GradientLoadingBar",
3 | "version": "3.0.0",
4 | "summary": "A customizable animated gradient loading bar.",
5 | "description": "A customizable animated gradient loading bar.\nInspired by https://codepen.io/marcobiedermann/pen/LExXWW",
6 | "homepage": "https://github.com/fxm90/GradientLoadingBar",
7 | "screenshots": "https://raw.githubusercontent.com/fxm90/GradientLoadingBar/master/Assets/screen.gif",
8 | "license": {
9 | "type": "MIT",
10 | "file": "LICENSE"
11 | },
12 | "authors": {
13 | "Felix Mau": "contact@felix.hamburg"
14 | },
15 | "source": {
16 | "git": "https://github.com/fxm90/GradientLoadingBar.git",
17 | "tag": "3.0.0"
18 | },
19 | "swift_versions": "5.5",
20 | "platforms": {
21 | "ios": "13.0"
22 | },
23 | "source_files": "GradientLoadingBar/Sources/**/*.swift",
24 | "testspecs": [
25 | {
26 | "name": "Tests",
27 | "test_type": "unit",
28 | "requires_app_host": true,
29 | "source_files": "GradientLoadingBar/Tests/**/*.{swift,md}",
30 | "dependencies": {
31 | "SnapshotTesting": [
32 | "~> 1.9"
33 | ]
34 | }
35 | }
36 | ],
37 | "swift_version": "5.5"
38 | }
39 |
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/SwiftConfigurationFiles.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SwiftConfigurationFiles",
3 | "version": "0.0.1",
4 | "summary": "Repository containing configuration files for Swift development, e.g. for tools like SwiftLint or SwiftFormat.",
5 | "homepage": "https://github.com/fxm90/Swift-Configuration-Files",
6 | "license": {
7 | "type": "MIT",
8 | "file": "LICENSE"
9 | },
10 | "authors": {
11 | "Felix Mau": "me@felix.hamburg"
12 | },
13 | "source": {
14 | "git": "https://github.com/fxm90/Swift-Configuration-Files.git",
15 | "tag": "0.0.1"
16 | },
17 | "social_media_url": "https://twitter.com/_fxm90",
18 | "preserve_paths": ".*",
19 | "platforms": {
20 | "osx": null,
21 | "ios": null,
22 | "tvos": null,
23 | "watchos": null
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Example/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - GradientLoadingBar (3.0.0)
3 | - GradientLoadingBar/Tests (3.0.0):
4 | - SnapshotTesting (~> 1.9)
5 | - SnapshotTesting (1.9.0)
6 | - SwiftConfigurationFiles (0.0.1)
7 | - SwiftFormat/CLI (0.50.1)
8 | - SwiftLint (0.49.1)
9 |
10 | DEPENDENCIES:
11 | - GradientLoadingBar (from `../`)
12 | - GradientLoadingBar/Tests (from `../`)
13 | - SwiftConfigurationFiles (from `https://github.com/fxm90/SwiftConfigurationFiles.git`)
14 | - SwiftFormat/CLI (~> 0.41)
15 | - SwiftLint (~> 0.42)
16 |
17 | SPEC REPOS:
18 | https://github.com/CocoaPods/Specs.git:
19 | - SnapshotTesting
20 | - SwiftFormat
21 | - SwiftLint
22 |
23 | EXTERNAL SOURCES:
24 | GradientLoadingBar:
25 | :path: "../"
26 | SwiftConfigurationFiles:
27 | :git: https://github.com/fxm90/SwiftConfigurationFiles.git
28 |
29 | CHECKOUT OPTIONS:
30 | SwiftConfigurationFiles:
31 | :commit: efe01ba39069e8957fb89217b8c7fed298254691
32 | :git: https://github.com/fxm90/SwiftConfigurationFiles.git
33 |
34 | SPEC CHECKSUMS:
35 | GradientLoadingBar: 3c4246535efdedaf9b471c05caabfdb4b87b40a2
36 | SnapshotTesting: 6141c48b6aa76ead61431ca665c14ab9a066c53b
37 | SwiftConfigurationFiles: 1cf2228a911ebed9f42f8dec077bb634f04ca6c8
38 | SwiftFormat: e73212c71908404e333da34e303772b9e516ac9b
39 | SwiftLint: 32ee33ded0636d0905ef6911b2b67bbaeeedafa5
40 |
41 | PODFILE CHECKSUM: ea7965c23e5364bcd5cd7d3693d685af73a3e31d
42 |
43 | COCOAPODS: 1.11.3
44 |
--------------------------------------------------------------------------------
/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/GradientLoadingBar-Unit-Tests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
63 |
64 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/GradientLoadingBar.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
53 |
54 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Point-Free, Inc.
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 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Async.swift:
--------------------------------------------------------------------------------
1 | /// A wrapper around an asynchronous operation.
2 | ///
3 | /// Snapshot strategies may utilize this type to create snapshots in an asynchronous fashion.
4 | ///
5 | /// For example, WebKit's `WKWebView` offers a callback-based API for taking image snapshots (`takeSnapshot`). `Async` allows us to build a value that can pass its callback along to the scope in which the image has been created.
6 | ///
7 | /// Async { callback in
8 | /// webView.takeSnapshot(with: nil) { image, error in
9 | /// callback(image!)
10 | /// }
11 | /// }
12 | public struct Async {
13 | public let run: (@escaping (Value) -> Void) -> Void
14 |
15 | /// Creates an asynchronous operation.
16 | ///
17 | /// - Parameters:
18 | /// - run: A function that, when called, can hand a value to a callback.
19 | /// - callback: A function that can be called with a value.
20 | public init(run: @escaping (_ callback: @escaping (Value) -> Void) -> Void) {
21 | self.run = run
22 | }
23 |
24 | /// Wraps a pure value in an asynchronous operation.
25 | ///
26 | /// - Parameter value: A value to be wrapped in an asynchronous operation.
27 | public init(value: Value) {
28 | self.init { callback in callback(value) }
29 | }
30 |
31 | /// Transforms an Async into an Async with a function `(Value) -> NewValue`.
32 | ///
33 | /// - Parameter f: A transformation to apply to the value wrapped by the async value.
34 | public func map(_ f: @escaping (Value) -> NewValue) -> Async {
35 | return .init { callback in
36 | self.run { a in callback(f(a)) }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/Internal.swift:
--------------------------------------------------------------------------------
1 | #if os(macOS)
2 | import Cocoa
3 | typealias Image = NSImage
4 | typealias ImageView = NSImageView
5 | typealias View = NSView
6 | #elseif os(iOS) || os(tvOS)
7 | import UIKit
8 | typealias Image = UIImage
9 | typealias ImageView = UIImageView
10 | typealias View = UIView
11 | #endif
12 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/String+SpecialCharacters.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension String {
4 |
5 | /// Checks whether the string has escaped special character literals or not.
6 | ///
7 | /// This method won't detect an unescaped special character.
8 | /// For example, this method will return true for "\\n" or #"\n"#, but false for "\n"
9 | ///
10 | /// The following are the special character literals that this methods looks for:
11 | /// The escaped special characters \0 (null character), \\ (backslash),
12 | /// \t (horizontal tab), \n (line feed), \r (carriage return),
13 | /// \" (double quotation mark) and \' (single quotation mark),
14 | /// An arbitrary Unicode scalar value, written as \u{n},
15 | /// where n is a 1–8 digit hexadecimal number (Unicode is discussed in Unicode below)
16 | /// The character sequence "#
17 | ///
18 | /// - Returns: True if the string has any special character literals, false otherwise.
19 | func hasEscapedSpecialCharactersLiteral() -> Bool {
20 | let multilineLiteralAndNumberSign = ##"""
21 | """#
22 | """##
23 | let patterns = [
24 | // Matches \u{n} where n is a 1–8 digit hexadecimal number
25 | try? NSRegularExpression(pattern: #"\\u\{[a-fA-f0-9]{1,8}\}"#, options: .init()),
26 | try? NSRegularExpression(pattern: #"\0"#, options: .ignoreMetacharacters),
27 | try? NSRegularExpression(pattern: #"\\"#, options: .ignoreMetacharacters),
28 | try? NSRegularExpression(pattern: #"\t"#, options: .ignoreMetacharacters),
29 | try? NSRegularExpression(pattern: #"\n"#, options: .ignoreMetacharacters),
30 | try? NSRegularExpression(pattern: #"\r"#, options: .ignoreMetacharacters),
31 | try? NSRegularExpression(pattern: #"\""#, options: .ignoreMetacharacters),
32 | try? NSRegularExpression(pattern: #"\'"#, options: .ignoreMetacharacters),
33 | try? NSRegularExpression(pattern: multilineLiteralAndNumberSign, options: .ignoreMetacharacters),
34 | ]
35 | let matches = patterns.compactMap { $0?.firstMatch(in: self, options: .init(), range: NSRange.init(location: 0, length: self.count)) }
36 | return matches.count > 0
37 | }
38 |
39 |
40 | /// This method calculates how many number signs (#) we need to add around a string
41 | /// literal to properly escape its content.
42 | ///
43 | /// Multiple # are needed when the literal contains "#, "##, "### ...
44 | ///
45 | /// - Returns: The number of "number signs(#)" needed around a string literal.
46 | /// When there is no "#, ... return 1
47 | func numberOfNumberSignsNeeded() -> Int {
48 | let pattern = try! NSRegularExpression(pattern: ##""#{1,}"##, options: .init())
49 |
50 | let matches = pattern.matches(in: self, options: .init(), range: NSRange.init(location: 0, length: self.count))
51 |
52 | // If we have "## then the length of the match is 3,
53 | // which is also the number of "number signs (#)" we need to add
54 | // before and after the string literal
55 | return matches.map { $0.range.length }.max() ?? 1
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/XCTAttachment.swift:
--------------------------------------------------------------------------------
1 | #if os(Linux)
2 | import Foundation
3 |
4 | public struct XCTAttachment {
5 | public init(data: Data) {}
6 | public init(data: Data, uniformTypeIdentifier: String) {}
7 | }
8 | #endif
9 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Diff.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct Difference {
4 | enum Which {
5 | case first
6 | case second
7 | case both
8 | }
9 |
10 | let elements: [A]
11 | let which: Which
12 | }
13 |
14 | func diff(_ fst: [A], _ snd: [A]) -> [Difference] {
15 | var idxsOf = [A: [Int]]()
16 | fst.enumerated().forEach { idxsOf[$1, default: []].append($0) }
17 |
18 | let sub = snd.enumerated().reduce((overlap: [Int: Int](), fst: 0, snd: 0, len: 0)) { sub, sndPair in
19 | (idxsOf[sndPair.element] ?? [])
20 | .reduce((overlap: [Int: Int](), fst: sub.fst, snd: sub.snd, len: sub.len)) { innerSub, fstIdx in
21 |
22 | var newOverlap = innerSub.overlap
23 | newOverlap[fstIdx] = (sub.overlap[fstIdx - 1] ?? 0) + 1
24 |
25 | if let newLen = newOverlap[fstIdx], newLen > sub.len {
26 | return (newOverlap, fstIdx - newLen + 1, sndPair.offset - newLen + 1, newLen)
27 | }
28 | return (newOverlap, innerSub.fst, innerSub.snd, innerSub.len)
29 | }
30 | }
31 | let (_, fstIdx, sndIdx, len) = sub
32 |
33 | if len == 0 {
34 | let fstDiff = fst.isEmpty ? [] : [Difference(elements: fst, which: .first)]
35 | let sndDiff = snd.isEmpty ? [] : [Difference(elements: snd, which: .second)]
36 | return fstDiff + sndDiff
37 | } else {
38 | let fstDiff = diff(Array(fst.prefix(upTo: fstIdx)), Array(snd.prefix(upTo: sndIdx)))
39 | let midDiff = [Difference(elements: Array(fst.suffix(from: fstIdx).prefix(len)), which: .both)]
40 | let lstDiff = diff(Array(fst.suffix(from: fstIdx + len)), Array(snd.suffix(from: sndIdx + len)))
41 | return fstDiff + midDiff + lstDiff
42 | }
43 | }
44 |
45 | let minus = "−"
46 | let plus = "+"
47 | private let figureSpace = "\u{2007}"
48 |
49 | struct Hunk {
50 | let fstIdx: Int
51 | let fstLen: Int
52 | let sndIdx: Int
53 | let sndLen: Int
54 | let lines: [String]
55 |
56 | var patchMark: String {
57 | let fstMark = "\(minus)\(fstIdx + 1),\(fstLen)"
58 | let sndMark = "\(plus)\(sndIdx + 1),\(sndLen)"
59 | return "@@ \(fstMark) \(sndMark) @@"
60 | }
61 |
62 | // Semigroup
63 |
64 | static func +(lhs: Hunk, rhs: Hunk) -> Hunk {
65 | return Hunk(
66 | fstIdx: lhs.fstIdx + rhs.fstIdx,
67 | fstLen: lhs.fstLen + rhs.fstLen,
68 | sndIdx: lhs.sndIdx + rhs.sndIdx,
69 | sndLen: lhs.sndLen + rhs.sndLen,
70 | lines: lhs.lines + rhs.lines
71 | )
72 | }
73 |
74 | // Monoid
75 |
76 | init(fstIdx: Int = 0, fstLen: Int = 0, sndIdx: Int = 0, sndLen: Int = 0, lines: [String] = []) {
77 | self.fstIdx = fstIdx
78 | self.fstLen = fstLen
79 | self.sndIdx = sndIdx
80 | self.sndLen = sndLen
81 | self.lines = lines
82 | }
83 |
84 | init(idx: Int = 0, len: Int = 0, lines: [String] = []) {
85 | self.init(fstIdx: idx, fstLen: len, sndIdx: idx, sndLen: len, lines: lines)
86 | }
87 | }
88 |
89 | func chunk(diff diffs: [Difference], context ctx: Int = 4) -> [Hunk] {
90 | func prepending(_ prefix: String) -> (String) -> String {
91 | return { prefix + $0 + ($0.hasSuffix(" ") ? "¬" : "") }
92 | }
93 | let changed: (Hunk) -> Bool = { $0.lines.contains(where: { $0.hasPrefix(minus) || $0.hasPrefix(plus) }) }
94 |
95 | let (hunk, hunks) = diffs
96 | .reduce((current: Hunk(), hunks: [Hunk]())) { cursor, diff in
97 | let (current, hunks) = cursor
98 | let len = diff.elements.count
99 |
100 | switch diff.which {
101 | case .both where len > ctx * 2:
102 | let hunk = current + Hunk(len: ctx, lines: diff.elements.prefix(ctx).map(prepending(figureSpace)))
103 | let next = Hunk(
104 | fstIdx: current.fstIdx + current.fstLen + len - ctx,
105 | fstLen: ctx,
106 | sndIdx: current.sndIdx + current.sndLen + len - ctx,
107 | sndLen: ctx,
108 | lines: (diff.elements.suffix(ctx) as ArraySlice).map(prepending(figureSpace))
109 | )
110 | return (next, changed(hunk) ? hunks + [hunk] : hunks)
111 | case .both where current.lines.isEmpty:
112 | let lines = (diff.elements.suffix(ctx) as ArraySlice).map(prepending(figureSpace))
113 | let count = lines.count
114 | return (current + Hunk(idx: len - count, len: count, lines: lines), hunks)
115 | case .both:
116 | return (current + Hunk(len: len, lines: diff.elements.map(prepending(figureSpace))), hunks)
117 | case .first:
118 | return (current + Hunk(fstLen: len, lines: diff.elements.map(prepending(minus))), hunks)
119 | case .second:
120 | return (current + Hunk(sndLen: len, lines: diff.elements.map(prepending(plus))), hunks)
121 | }
122 | }
123 |
124 | return changed(hunk) ? hunks + [hunk] : hunks
125 | }
126 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Diffing.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 |
4 | /// The ability to compare `Value`s and convert them to and from `Data`.
5 | public struct Diffing {
6 | /// Converts a value _to_ data.
7 | public var toData: (Value) -> Data
8 |
9 | /// Produces a value _from_ data.
10 | public var fromData: (Data) -> Value
11 |
12 | /// Compares two values. If the values do not match, returns a failure message and artifacts describing the failure.
13 | public var diff: (Value, Value) -> (String, [XCTAttachment])?
14 |
15 | /// Creates a new `Diffing` on `Value`.
16 | ///
17 | /// - Parameters:
18 | /// - toData: A function used to convert a value _to_ data.
19 | /// - value: A value to convert into data.
20 | /// - fromData: A function used to produce a value _from_ data.
21 | /// - data: Data to convert into a value.
22 | /// - diff: A function used to compare two values. If the values do not match, returns a failure message and artifacts describing the failure.
23 | /// - lhs: A value to compare.
24 | /// - rhs: Another value to compare.
25 | public init(
26 | toData: @escaping (_ value: Value) -> Data,
27 | fromData: @escaping (_ data: Data) -> Value,
28 | diff: @escaping (_ lhs: Value, _ rhs: Value) -> (String, [XCTAttachment])?
29 | ) {
30 | self.toData = toData
31 | self.fromData = fromData
32 | self.diff = diff
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Extensions/Wait.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 |
4 | extension Snapshotting {
5 | /// Transforms an existing snapshot strategy into one that waits for some amount of time before taking the snapshot. This can be useful for waiting for animations to complete or for UIKit events to finish (i.e. waiting for a UINavigationController to push a child onto the stack).
6 | /// - Parameters:
7 | /// - duration: The amount of time to wait before taking the snapshot.
8 | /// - strategy: The snapshot to invoke after the specified amount of time has passed.
9 | public static func wait(
10 | for duration: TimeInterval,
11 | on strategy: Snapshotting
12 | ) -> Snapshotting {
13 | return Snapshotting(
14 | pathExtension: strategy.pathExtension,
15 | diffing: strategy.diffing,
16 | asyncSnapshot: { value in
17 | Async { callback in
18 | let expectation = XCTestExpectation(description: "Wait")
19 | DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
20 | expectation.fulfill()
21 | }
22 | _ = XCTWaiter.wait(for: [expectation], timeout: duration + 1)
23 | strategy.snapshot(value).run(callback)
24 | }
25 | })
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/SnapshotTestCase.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | @available(swift, obsoleted: 5.0, renamed: "XCTestCase", message: "Please use XCTestCase instead")
4 | public typealias SnapshotTestCase = XCTestCase
5 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 |
4 | /// A type representing the ability to transform a snapshottable value into a diffable format (like text or an image) for snapshot testing.
5 | public struct Snapshotting {
6 | /// The path extension applied to references saved to disk.
7 | public var pathExtension: String?
8 |
9 | /// How the snapshot format is diffed and converted to and from data.
10 | public var diffing: Diffing
11 |
12 | /// How a value is transformed into a diffable snapshot format.
13 | public var snapshot: (Value) -> Async
14 |
15 | /// Creates a snapshot strategy.
16 | ///
17 | /// - Parameters:
18 | /// - pathExtension: The path extension applied to references saved to disk.
19 | /// - diffing: How to diff and convert the snapshot format to and from data.
20 | /// - snapshot: An asynchronous transform function from a value into a diffable snapshot format.
21 | /// - value: A value to be converted.
22 | public init(
23 | pathExtension: String?,
24 | diffing: Diffing,
25 | asyncSnapshot: @escaping (_ value: Value) -> Async
26 | ) {
27 | self.pathExtension = pathExtension
28 | self.diffing = diffing
29 | self.snapshot = asyncSnapshot
30 | }
31 |
32 | /// Creates a snapshot strategy.
33 | ///
34 | /// - Parameters:
35 | /// - pathExtension: The path extension applied to references saved to disk.
36 | /// - diffing: How to diff and convert the snapshot format to and from data.
37 | /// - snapshot: A transform function from a value into a diffable snapshot format.
38 | /// - value: A snapshot value to be converted.
39 | public init(
40 | pathExtension: String?,
41 | diffing: Diffing,
42 | snapshot: @escaping (_ value: Value) -> Format
43 | ) {
44 | self.init(pathExtension: pathExtension, diffing: diffing) {
45 | Async(value: snapshot($0))
46 | }
47 | }
48 |
49 | /// Transforms a strategy on `Value`s into a strategy on `NewValue`s through a function `(NewValue) -> Value`.
50 | ///
51 | /// This is the most important operation for transforming existing strategies into new strategies. It allows you to transform a `Snapshotting` into a `Snapshotting` by pulling it back along a function `(NewValue) -> Value`. Notice that the function must go in the direction `(NewValue) -> Value` even though we are transforming in the other direction `(Snapshotting) -> Snapshotting`.
52 | ///
53 | /// A simple example of this is to `pullback` the snapshot strategy on `UIView`s to work on `UIViewController`s:
54 | ///
55 | /// let strategy = Snapshotting.image.pullback { (vc: UIViewController) in
56 | /// return vc.view
57 | /// }
58 | ///
59 | /// Here we took the strategy that snapshots `UIView`s as `UIImage`s and pulled it back to work on `UIViewController`s by using the function `(UIViewController) -> UIView` that simply plucks the view out of the controller.
60 | ///
61 | /// Nearly every snapshot strategy provided in this library is a pullback of some base strategy, which shows just how important this operation is.
62 | ///
63 | /// - Parameters:
64 | /// - transform: A transform function from `NewValue` into `Value`.
65 | /// - otherValue: A value to be transformed.
66 | public func pullback(_ transform: @escaping (_ otherValue: NewValue) -> Value) -> Snapshotting {
67 | return self.asyncPullback { newValue in Async(value: transform(newValue)) }
68 | }
69 |
70 | /// Transforms a strategy on `Value`s into a strategy on `NewValue`s through a function `(NewValue) -> Async`.
71 | ///
72 | /// See the documention of `pullback` for a full description of how pullbacks works. This operation differs from `pullback` in that it allows you to use a transformation `(NewValue) -> Async`, which is necessary when your transformation needs to perform some asynchronous work.
73 | ///
74 | /// - Parameters:
75 | /// - transform: A transform function from `NewValue` into `Async`.
76 | /// - otherValue: A value to be transformed.
77 | public func asyncPullback(_ transform: @escaping (_ otherValue: NewValue) -> Async)
78 | -> Snapshotting {
79 |
80 | return Snapshotting(
81 | pathExtension: self.pathExtension,
82 | diffing: self.diffing
83 | ) { newValue in
84 | return .init { callback in
85 | transform(newValue).run { value in
86 | self.snapshot(value).run { snapshot in
87 | callback(snapshot)
88 | }
89 | }
90 | }
91 | }
92 | }
93 | }
94 |
95 | /// A snapshot strategy where the type being snapshot is also a diffable type.
96 | public typealias SimplySnapshotting = Snapshotting
97 |
98 | extension Snapshotting where Value == Format {
99 | public init(pathExtension: String?, diffing: Diffing) {
100 | self.init(
101 | pathExtension: pathExtension,
102 | diffing: diffing,
103 | snapshot: { $0 }
104 | )
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Any.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Snapshotting where Format == String {
4 | /// A snapshot strategy for comparing any structure based on a sanitized text dump.
5 | public static var dump: Snapshotting {
6 | return SimplySnapshotting.lines.pullback { snap($0) }
7 | }
8 | }
9 |
10 | private func snap(_ value: T, name: String? = nil, indent: Int = 0) -> String {
11 | let indentation = String(repeating: " ", count: indent)
12 | let mirror = Mirror(reflecting: value)
13 | var children = mirror.children
14 | let count = children.count
15 | let bullet = count == 0 ? "-" : "▿"
16 |
17 | let description: String
18 | switch (value, mirror.displayStyle) {
19 | case (_, .collection?):
20 | description = count == 1 ? "1 element" : "\(count) elements"
21 | case (_, .dictionary?):
22 | description = count == 1 ? "1 key/value pair" : "\(count) key/value pairs"
23 | children = sort(children)
24 | case (_, .set?):
25 | description = count == 1 ? "1 member" : "\(count) members"
26 | children = sort(children)
27 | case (_, .tuple?):
28 | description = count == 1 ? "(1 element)" : "(\(count) elements)"
29 | case (_, .optional?):
30 | let subjectType = String(describing: mirror.subjectType)
31 | .replacingOccurrences(of: " #\\d+", with: "", options: .regularExpression)
32 | description = count == 0 ? "\(subjectType).none" : "\(subjectType)"
33 | case (let value as AnySnapshotStringConvertible, _) where type(of: value).renderChildren:
34 | description = value.snapshotDescription
35 | case (let value as AnySnapshotStringConvertible, _):
36 | return "\(indentation)- \(name.map { "\($0): " } ?? "")\(value.snapshotDescription)\n"
37 | case (let value as CustomStringConvertible, _):
38 | description = value.description
39 | case (_, .class?), (_, .struct?):
40 | description = String(describing: mirror.subjectType)
41 | .replacingOccurrences(of: " #\\d+", with: "", options: .regularExpression)
42 | children = sort(children)
43 | case (_, .enum?):
44 | let subjectType = String(describing: mirror.subjectType)
45 | .replacingOccurrences(of: " #\\d+", with: "", options: .regularExpression)
46 | description = count == 0 ? "\(subjectType).\(value)" : "\(subjectType)"
47 | case (let value, _):
48 | description = String(describing: value)
49 | }
50 |
51 | let lines = ["\(indentation)\(bullet) \(name.map { "\($0): " } ?? "")\(description)\n"]
52 | + children.map { snap($1, name: $0, indent: indent + 2) }
53 |
54 | return lines.joined()
55 | }
56 |
57 | private func sort(_ children: Mirror.Children) -> Mirror.Children {
58 | return .init(
59 | children
60 | .map({ (child: $0, snap: snap($0)) })
61 | .sorted(by: { $0.snap < $1.snap })
62 | .map({ $0.child })
63 | )
64 | }
65 |
66 | /// A type with a customized snapshot dump representation.
67 | ///
68 | /// Types that conform to the `AnySnapshotStringConvertible` protocol can provide their own representation to be used when converting an instance to a `dump`-based snapshot.
69 | public protocol AnySnapshotStringConvertible {
70 | /// Whether or not to dump child nodes (defaults to `false`).
71 | static var renderChildren: Bool { get }
72 |
73 | /// A textual snapshot dump representation of this instance.
74 | var snapshotDescription: String { get }
75 | }
76 |
77 | extension AnySnapshotStringConvertible {
78 | public static var renderChildren: Bool {
79 | return false
80 | }
81 | }
82 |
83 | extension Character: AnySnapshotStringConvertible {
84 | public var snapshotDescription: String {
85 | return self.debugDescription
86 | }
87 | }
88 |
89 | extension Data: AnySnapshotStringConvertible {
90 | public var snapshotDescription: String {
91 | return self.debugDescription
92 | }
93 | }
94 |
95 | extension Date: AnySnapshotStringConvertible {
96 | public var snapshotDescription: String {
97 | return snapshotDateFormatter.string(from: self)
98 | }
99 | }
100 |
101 | extension NSObject: AnySnapshotStringConvertible {
102 | #if canImport(ObjectiveC)
103 | @objc open var snapshotDescription: String {
104 | return purgePointers(self.debugDescription)
105 | }
106 | #else
107 | open var snapshotDescription: String {
108 | return purgePointers(self.debugDescription)
109 | }
110 | #endif
111 | }
112 |
113 | extension String: AnySnapshotStringConvertible {
114 | public var snapshotDescription: String {
115 | return self.debugDescription
116 | }
117 | }
118 |
119 | extension Substring: AnySnapshotStringConvertible {
120 | public var snapshotDescription: String {
121 | return self.debugDescription
122 | }
123 | }
124 |
125 | extension URL: AnySnapshotStringConvertible {
126 | public var snapshotDescription: String {
127 | return self.debugDescription
128 | }
129 | }
130 |
131 | private let snapshotDateFormatter: DateFormatter = {
132 | let formatter = DateFormatter()
133 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
134 | formatter.calendar = Calendar(identifier: .gregorian)
135 | formatter.locale = Locale(identifier: "en_US_POSIX")
136 | formatter.timeZone = TimeZone(abbreviation: "UTC")
137 | return formatter
138 | }()
139 |
140 | func purgePointers(_ string: String) -> String {
141 | return string.replacingOccurrences(of: ":?\\s*0x[\\da-f]+(\\s*)", with: "$1", options: .regularExpression)
142 | }
143 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CALayer.swift:
--------------------------------------------------------------------------------
1 | #if os(macOS)
2 | import Cocoa
3 |
4 | extension Snapshotting where Value == CALayer, Format == NSImage {
5 | /// A snapshot strategy for comparing layers based on pixel equality.
6 | public static var image: Snapshotting {
7 | return .image(precision: 1)
8 | }
9 |
10 | /// A snapshot strategy for comparing layers based on pixel equality.
11 | ///
12 | /// - Parameter precision: The percentage of pixels that must match.
13 | public static func image(precision: Float) -> Snapshotting {
14 | return SimplySnapshotting.image(precision: precision).pullback { layer in
15 | let image = NSImage(size: layer.bounds.size)
16 | image.lockFocus()
17 | let context = NSGraphicsContext.current!.cgContext
18 | layer.setNeedsLayout()
19 | layer.layoutIfNeeded()
20 | layer.render(in: context)
21 | image.unlockFocus()
22 | return image
23 | }
24 | }
25 | }
26 | #elseif os(iOS) || os(tvOS)
27 | import UIKit
28 |
29 | extension Snapshotting where Value == CALayer, Format == UIImage {
30 | /// A snapshot strategy for comparing layers based on pixel equality.
31 | public static var image: Snapshotting {
32 | return .image()
33 | }
34 |
35 | /// A snapshot strategy for comparing layers based on pixel equality.
36 | ///
37 | /// - Parameter precision: The percentage of pixels that must match.
38 | public static func image(precision: Float = 1, traits: UITraitCollection = .init())
39 | -> Snapshotting {
40 | return SimplySnapshotting.image(precision: precision, scale: traits.displayScale).pullback { layer in
41 | renderer(bounds: layer.bounds, for: traits).image { ctx in
42 | layer.setNeedsLayout()
43 | layer.layoutIfNeeded()
44 | layer.render(in: ctx.cgContext)
45 | }
46 | }
47 | }
48 | }
49 | #endif
50 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CGPath.swift:
--------------------------------------------------------------------------------
1 | #if os(macOS)
2 | import Cocoa
3 |
4 | extension Snapshotting where Value == CGPath, Format == NSImage {
5 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
6 | public static var image: Snapshotting {
7 | return .image()
8 | }
9 |
10 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
11 | ///
12 | /// - Parameter precision: The percentage of pixels that must match.
13 | public static func image(precision: Float = 1, drawingMode: CGPathDrawingMode = .eoFill) -> Snapshotting {
14 | return SimplySnapshotting.image(precision: precision).pullback { path in
15 | let bounds = path.boundingBoxOfPath
16 | var transform = CGAffineTransform(translationX: -bounds.origin.x, y: -bounds.origin.y)
17 | let path = path.copy(using: &transform)!
18 |
19 | let image = NSImage(size: bounds.size)
20 | image.lockFocus()
21 | let context = NSGraphicsContext.current!.cgContext
22 |
23 | context.addPath(path)
24 | context.drawPath(using: drawingMode)
25 | image.unlockFocus()
26 | return image
27 | }
28 | }
29 | }
30 | #elseif os(iOS) || os(tvOS)
31 | import UIKit
32 |
33 | extension Snapshotting where Value == CGPath, Format == UIImage {
34 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
35 | public static var image: Snapshotting {
36 | return .image()
37 | }
38 |
39 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
40 | ///
41 | /// - Parameter precision: The percentage of pixels that must match.
42 | public static func image(precision: Float = 1, scale: CGFloat = 1, drawingMode: CGPathDrawingMode = .eoFill) -> Snapshotting {
43 | return SimplySnapshotting.image(precision: precision, scale: scale).pullback { path in
44 | let bounds = path.boundingBoxOfPath
45 | let format: UIGraphicsImageRendererFormat
46 | if #available(iOS 11.0, tvOS 11.0, *) {
47 | format = UIGraphicsImageRendererFormat.preferred()
48 | } else {
49 | format = UIGraphicsImageRendererFormat.default()
50 | }
51 | format.scale = scale
52 | return UIGraphicsImageRenderer(bounds: bounds, format: format).image { ctx in
53 | let cgContext = ctx.cgContext
54 | cgContext.addPath(path)
55 | cgContext.drawPath(using: drawingMode)
56 | }
57 | }
58 | }
59 | }
60 | #endif
61 |
62 | #if os(macOS) || os(iOS) || os(tvOS)
63 | @available(iOS 11.0, OSX 10.13, tvOS 11.0, *)
64 | extension Snapshotting where Value == CGPath, Format == String {
65 | /// A snapshot strategy for comparing bezier paths based on element descriptions.
66 | public static var elementsDescription: Snapshotting {
67 | return .elementsDescription(numberFormatter: defaultNumberFormatter)
68 | }
69 |
70 | /// A snapshot strategy for comparing bezier paths based on element descriptions.
71 | ///
72 | /// - Parameter numberFormatter: The number formatter used for formatting points.
73 | public static func elementsDescription(numberFormatter: NumberFormatter) -> Snapshotting {
74 | let namesByType: [CGPathElementType: String] = [
75 | .moveToPoint: "MoveTo",
76 | .addLineToPoint: "LineTo",
77 | .addQuadCurveToPoint: "QuadCurveTo",
78 | .addCurveToPoint: "CurveTo",
79 | .closeSubpath: "Close",
80 | ]
81 |
82 | let numberOfPointsByType: [CGPathElementType: Int] = [
83 | .moveToPoint: 1,
84 | .addLineToPoint: 1,
85 | .addQuadCurveToPoint: 2,
86 | .addCurveToPoint: 3,
87 | .closeSubpath: 0,
88 | ]
89 |
90 | return SimplySnapshotting.lines.pullback { path in
91 | var string: String = ""
92 |
93 | path.applyWithBlock { elementPointer in
94 | let element = elementPointer.pointee
95 | let name = namesByType[element.type] ?? "Unknown"
96 |
97 | if element.type == .moveToPoint && !string.isEmpty {
98 | string += "\n"
99 | }
100 |
101 | string += name
102 |
103 | if let numberOfPoints = numberOfPointsByType[element.type] {
104 | let points = UnsafeBufferPointer(start: element.points, count: numberOfPoints)
105 | string += " " + points.map { point in
106 | let x = numberFormatter.string(from: point.x as NSNumber)!
107 | let y = numberFormatter.string(from: point.y as NSNumber)!
108 | return "(\(x), \(y))"
109 | }.joined(separator: " ")
110 | }
111 |
112 | string += "\n"
113 | }
114 |
115 | return string
116 | }
117 | }
118 | }
119 |
120 | private let defaultNumberFormatter: NumberFormatter = {
121 | let numberFormatter = NumberFormatter()
122 | numberFormatter.minimumFractionDigits = 1
123 | numberFormatter.maximumFractionDigits = 3
124 | return numberFormatter
125 | }()
126 | #endif
127 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CaseIterable.swift:
--------------------------------------------------------------------------------
1 | extension Snapshotting where Value: CaseIterable, Format == String {
2 | /// A strategy for snapshotting the output for every input of a function. The format of the snapshot
3 | /// is a comma-separated value (CSV) file that shows the mapping of inputs to outputs.
4 | ///
5 | /// Parameter witness: A snapshotting value on the output of the function to be snapshot.
6 | /// Returns: A snapshot strategy on functions (Value) -> A that feeds every possible input into the
7 | /// function and records the output into a CSV file.
8 | public static func `func`(into witness: Snapshotting) -> Snapshotting<(Value) -> A, Format> {
9 | var snapshotting = Snapshotting.lines.asyncPullback { (f: (Value) -> A) in
10 | Value.allCases.map { input in
11 | witness.snapshot(f(input))
12 | .map { (input, $0) }
13 | }
14 | .sequence()
15 | .map { rows in
16 | rows.map { "\"\($0)\",\"\($1)\"" }
17 | .joined(separator: "\n")
18 | }
19 | }
20 |
21 | snapshotting.pathExtension = "csv"
22 |
23 | return snapshotting
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Codable.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Snapshotting where Value: Encodable, Format == String {
4 | /// A snapshot strategy for comparing encodable structures based on their JSON representation.
5 | @available(iOS 11.0, macOS 10.13, tvOS 11.0, *)
6 | public static var json: Snapshotting {
7 | let encoder = JSONEncoder()
8 | encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
9 | return .json(encoder)
10 | }
11 |
12 | /// A snapshot strategy for comparing encodable structures based on their JSON representation.
13 | ///
14 | /// - Parameter encoder: A JSON encoder.
15 | public static func json(_ encoder: JSONEncoder) -> Snapshotting {
16 | var snapshotting = SimplySnapshotting.lines.pullback { (encodable: Value) in
17 | try! String(decoding: encoder.encode(encodable), as: UTF8.self)
18 | }
19 | snapshotting.pathExtension = "json"
20 | return snapshotting
21 | }
22 |
23 | /// A snapshot strategy for comparing encodable structures based on their property list representation.
24 | public static var plist: Snapshotting {
25 | let encoder = PropertyListEncoder()
26 | encoder.outputFormat = .xml
27 | return .plist(encoder)
28 | }
29 |
30 | /// A snapshot strategy for comparing encodable structures based on their property list representation.
31 | ///
32 | /// - Parameter encoder: A property list encoder.
33 | public static func plist(_ encoder: PropertyListEncoder) -> Snapshotting {
34 | var snapshotting = SimplySnapshotting.lines.pullback { (encodable: Value) in
35 | try! String(decoding: encoder.encode(encodable), as: UTF8.self)
36 | }
37 | snapshotting.pathExtension = "plist"
38 | return snapshotting
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Data.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 |
4 | extension Snapshotting where Value == Data, Format == Data {
5 | public static var data: Snapshotting {
6 | return .init(
7 | pathExtension: nil,
8 | diffing: .init(toData: { $0 }, fromData: { $0 }) { old, new in
9 | guard old != new else { return nil }
10 | let message = old.count == new.count
11 | ? "Expected data to match"
12 | : "Expected \(new) to match \(old)"
13 | return (message, [])
14 | }
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Description.swift:
--------------------------------------------------------------------------------
1 | extension Snapshotting where Format == String {
2 | /// A snapshot strategy that captures a value's textual description from `String`'s `init(description:)`
3 | /// initializer.
4 | public static var description: Snapshotting {
5 | return SimplySnapshotting.lines.pullback(String.init(describing:))
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSBezierPath.swift:
--------------------------------------------------------------------------------
1 | #if os(macOS)
2 | import Cocoa
3 |
4 | extension Snapshotting where Value == NSBezierPath, Format == NSImage {
5 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
6 | public static var image: Snapshotting {
7 | return .image()
8 | }
9 |
10 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
11 | ///
12 | /// - Parameter precision: The percentage of pixels that must match.
13 | public static func image(precision: Float = 1) -> Snapshotting {
14 | return SimplySnapshotting.image(precision: precision).pullback { path in
15 | // Move path info frame:
16 | let bounds = path.bounds
17 | let transform = AffineTransform(translationByX: -bounds.origin.x, byY: -bounds.origin.y)
18 | path.transform(using: transform)
19 |
20 | let image = NSImage(size: path.bounds.size)
21 | image.lockFocus()
22 | path.fill()
23 | image.unlockFocus()
24 | return image
25 | }
26 | }
27 | }
28 |
29 | extension Snapshotting where Value == NSBezierPath, Format == String {
30 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
31 | @available(iOS 11.0, *)
32 | public static var elementsDescription: Snapshotting {
33 | return .elementsDescription(numberFormatter: defaultNumberFormatter)
34 | }
35 |
36 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
37 | ///
38 | /// - Parameter numberFormatter: The number formatter used for formatting points.
39 | @available(iOS 11.0, *)
40 | public static func elementsDescription(numberFormatter: NumberFormatter) -> Snapshotting {
41 | let namesByType: [NSBezierPath.ElementType: String] = [
42 | .moveTo: "MoveTo",
43 | .lineTo: "LineTo",
44 | .curveTo: "CurveTo",
45 | .closePath: "Close",
46 | ]
47 |
48 | let numberOfPointsByType: [NSBezierPath.ElementType: Int] = [
49 | .moveTo: 1,
50 | .lineTo: 1,
51 | .curveTo: 3,
52 | .closePath: 0,
53 | ]
54 |
55 | return SimplySnapshotting.lines.pullback { path in
56 | var string: String = ""
57 |
58 | var elementPoints = [CGPoint](repeating: .zero, count: 3)
59 | for elementIndex in 0.. Diffing {
14 | return .init(
15 | toData: { NSImagePNGRepresentation($0)! },
16 | fromData: { NSImage(data: $0)! }
17 | ) { old, new in
18 | guard !compare(old, new, precision: precision) else { return nil }
19 | let difference = SnapshotTesting.diff(old, new)
20 | let message = new.size == old.size
21 | ? "Newly-taken snapshot does not match reference."
22 | : "Newly-taken snapshot@\(new.size) does not match reference@\(old.size)."
23 | return (
24 | message,
25 | [XCTAttachment(image: old), XCTAttachment(image: new), XCTAttachment(image: difference)]
26 | )
27 | }
28 | }
29 | }
30 |
31 | extension Snapshotting where Value == NSImage, Format == NSImage {
32 | /// A snapshot strategy for comparing images based on pixel equality.
33 | public static var image: Snapshotting {
34 | return .image(precision: 1)
35 | }
36 |
37 | /// A snapshot strategy for comparing images based on pixel equality.
38 | ///
39 | /// - Parameter precision: The percentage of pixels that must match.
40 | public static func image(precision: Float) -> Snapshotting {
41 | return .init(
42 | pathExtension: "png",
43 | diffing: .image(precision: precision)
44 | )
45 | }
46 | }
47 |
48 | private func NSImagePNGRepresentation(_ image: NSImage) -> Data? {
49 | guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return nil }
50 | let rep = NSBitmapImageRep(cgImage: cgImage)
51 | rep.size = image.size
52 | return rep.representation(using: .png, properties: [:])
53 | }
54 |
55 | private func compare(_ old: NSImage, _ new: NSImage, precision: Float) -> Bool {
56 | guard let oldCgImage = old.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false }
57 | guard let newCgImage = new.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false }
58 | guard oldCgImage.width != 0 else { return false }
59 | guard newCgImage.width != 0 else { return false }
60 | guard oldCgImage.width == newCgImage.width else { return false }
61 | guard oldCgImage.height != 0 else { return false }
62 | guard newCgImage.height != 0 else { return false }
63 | guard oldCgImage.height == newCgImage.height else { return false }
64 | guard let oldContext = context(for: oldCgImage) else { return false }
65 | guard let newContext = context(for: newCgImage) else { return false }
66 | guard let oldData = oldContext.data else { return false }
67 | guard let newData = newContext.data else { return false }
68 | let byteCount = oldContext.height * oldContext.bytesPerRow
69 | if memcmp(oldData, newData, byteCount) == 0 { return true }
70 | let newer = NSImage(data: NSImagePNGRepresentation(new)!)!
71 | guard let newerCgImage = newer.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false }
72 | guard let newerContext = context(for: newerCgImage) else { return false }
73 | guard let newerData = newerContext.data else { return false }
74 | if memcmp(oldData, newerData, byteCount) == 0 { return true }
75 | if precision >= 1 { return false }
76 | let oldRep = NSBitmapImageRep(cgImage: oldCgImage)
77 | let newRep = NSBitmapImageRep(cgImage: newerCgImage)
78 | var differentPixelCount = 0
79 | let pixelCount = oldRep.pixelsWide * oldRep.pixelsHigh
80 | let threshold = (1 - precision) * Float(pixelCount)
81 | let p1: UnsafeMutablePointer = oldRep.bitmapData!
82 | let p2: UnsafeMutablePointer = newRep.bitmapData!
83 | for offset in 0 ..< pixelCount * 4 {
84 | if p1[offset] != p2[offset] {
85 | differentPixelCount += 1
86 | }
87 | if Float(differentPixelCount) > threshold { return false }
88 | }
89 | return true
90 | }
91 |
92 | private func context(for cgImage: CGImage) -> CGContext? {
93 | guard
94 | let space = cgImage.colorSpace,
95 | let context = CGContext(
96 | data: nil,
97 | width: cgImage.width,
98 | height: cgImage.height,
99 | bitsPerComponent: cgImage.bitsPerComponent,
100 | bytesPerRow: cgImage.bytesPerRow,
101 | space: space,
102 | bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
103 | )
104 | else { return nil }
105 |
106 | context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height))
107 | return context
108 | }
109 |
110 | private func diff(_ old: NSImage, _ new: NSImage) -> NSImage {
111 | let oldCiImage = CIImage(cgImage: old.cgImage(forProposedRect: nil, context: nil, hints: nil)!)
112 | let newCiImage = CIImage(cgImage: new.cgImage(forProposedRect: nil, context: nil, hints: nil)!)
113 | let differenceFilter = CIFilter(name: "CIDifferenceBlendMode")!
114 | differenceFilter.setValue(oldCiImage, forKey: kCIInputImageKey)
115 | differenceFilter.setValue(newCiImage, forKey: kCIInputBackgroundImageKey)
116 | let maxSize = CGSize(
117 | width: max(old.size.width, new.size.width),
118 | height: max(old.size.height, new.size.height)
119 | )
120 | let rep = NSCIImageRep(ciImage: differenceFilter.outputImage!)
121 | let difference = NSImage(size: maxSize)
122 | difference.addRepresentation(rep)
123 | return difference
124 | }
125 | #endif
126 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSView.swift:
--------------------------------------------------------------------------------
1 | #if os(macOS)
2 | import Cocoa
3 |
4 | extension Snapshotting where Value == NSView, Format == NSImage {
5 | /// A snapshot strategy for comparing views based on pixel equality.
6 | public static var image: Snapshotting {
7 | return .image()
8 | }
9 |
10 | /// A snapshot strategy for comparing views based on pixel equality.
11 | ///
12 | /// - Parameters:
13 | /// - precision: The percentage of pixels that must match.
14 | /// - size: A view size override.
15 | public static func image(precision: Float = 1, size: CGSize? = nil) -> Snapshotting {
16 | return SimplySnapshotting.image(precision: precision).asyncPullback { view in
17 | let initialSize = view.frame.size
18 | if let size = size { view.frame.size = size }
19 | guard view.frame.width > 0, view.frame.height > 0 else {
20 | fatalError("View not renderable to image at size \(view.frame.size)")
21 | }
22 | return view.snapshot ?? Async { callback in
23 | addImagesForRenderedViews(view).sequence().run { views in
24 | let bitmapRep = view.bitmapImageRepForCachingDisplay(in: view.bounds)!
25 | view.cacheDisplay(in: view.bounds, to: bitmapRep)
26 | let image = NSImage(size: view.bounds.size)
27 | image.addRepresentation(bitmapRep)
28 | callback(image)
29 | views.forEach { $0.removeFromSuperview() }
30 | view.frame.size = initialSize
31 | }
32 | }
33 | }
34 | }
35 | }
36 |
37 | extension Snapshotting where Value == NSView, Format == String {
38 | /// A snapshot strategy for comparing views based on a recursive description of their properties and hierarchies.
39 | public static var recursiveDescription: Snapshotting {
40 | return SimplySnapshotting.lines.pullback { view in
41 | return purgePointers(
42 | view.perform(Selector(("_subtreeDescription"))).retain().takeUnretainedValue()
43 | as! String
44 | )
45 | }
46 | }
47 | }
48 | #endif
49 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSViewController.swift:
--------------------------------------------------------------------------------
1 | #if os(macOS)
2 | import Cocoa
3 |
4 | extension Snapshotting where Value == NSViewController, Format == NSImage {
5 | /// A snapshot strategy for comparing view controller views based on pixel equality.
6 | public static var image: Snapshotting {
7 | return .image()
8 | }
9 |
10 | /// A snapshot strategy for comparing view controller views based on pixel equality.
11 | ///
12 | /// - Parameters:
13 | /// - precision: The percentage of pixels that must match.
14 | /// - size: A view size override.
15 | public static func image(precision: Float = 1, size: CGSize? = nil) -> Snapshotting {
16 | return Snapshotting.image(precision: precision, size: size).pullback { $0.view }
17 | }
18 | }
19 |
20 | extension Snapshotting where Value == NSViewController, Format == String {
21 | /// A snapshot strategy for comparing view controller views based on a recursive description of their properties and hierarchies.
22 | public static var recursiveDescription: Snapshotting {
23 | return Snapshotting.recursiveDescription.pullback { $0.view }
24 | }
25 | }
26 | #endif
27 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SceneKit.swift:
--------------------------------------------------------------------------------
1 | #if os(iOS) || os(macOS) || os(tvOS)
2 | import SceneKit
3 | #if os(macOS)
4 | import Cocoa
5 | #elseif os(iOS) || os(tvOS)
6 | import UIKit
7 | #endif
8 |
9 | #if os(macOS)
10 | extension Snapshotting where Value == SCNScene, Format == NSImage {
11 | /// A snapshot strategy for comparing SceneKit scenes based on pixel equality.
12 | ///
13 | /// - Parameters:
14 | /// - precision: The percentage of pixels that must match.
15 | /// - size: The size of the scene.
16 | public static func image(precision: Float = 1, size: CGSize) -> Snapshotting {
17 | return .scnScene(precision: precision, size: size)
18 | }
19 | }
20 | #elseif os(iOS) || os(tvOS)
21 | extension Snapshotting where Value == SCNScene, Format == UIImage {
22 | /// A snapshot strategy for comparing SceneKit scenes based on pixel equality.
23 | ///
24 | /// - Parameters:
25 | /// - precision: The percentage of pixels that must match.
26 | /// - size: The size of the scene.
27 | public static func image(precision: Float = 1, size: CGSize) -> Snapshotting {
28 | return .scnScene(precision: precision, size: size)
29 | }
30 | }
31 | #endif
32 |
33 | fileprivate extension Snapshotting where Value == SCNScene, Format == Image {
34 | static func scnScene(precision: Float, size: CGSize) -> Snapshotting {
35 | return Snapshotting.image(precision: precision).pullback { scene in
36 | let view = SCNView(frame: .init(x: 0, y: 0, width: size.width, height: size.height))
37 | view.scene = scene
38 | return view
39 | }
40 | }
41 | }
42 | #endif
43 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SpriteKit.swift:
--------------------------------------------------------------------------------
1 | #if os(iOS) || os(macOS) || os(tvOS)
2 | import SpriteKit
3 | #if os(macOS)
4 | import Cocoa
5 | #elseif os(iOS) || os(tvOS)
6 | import UIKit
7 | #endif
8 |
9 | #if os(macOS)
10 | extension Snapshotting where Value == SKScene, Format == NSImage {
11 | /// A snapshot strategy for comparing SpriteKit scenes based on pixel equality.
12 | ///
13 | /// - Parameters:
14 | /// - precision: The percentage of pixels that must match.
15 | /// - size: The size of the scene.
16 | public static func image(precision: Float = 1, size: CGSize) -> Snapshotting {
17 | return .skScene(precision: precision, size: size)
18 | }
19 | }
20 | #elseif os(iOS) || os(tvOS)
21 | extension Snapshotting where Value == SKScene, Format == UIImage {
22 | /// A snapshot strategy for comparing SpriteKit scenes based on pixel equality.
23 | ///
24 | /// - Parameters:
25 | /// - precision: The percentage of pixels that must match.
26 | /// - size: The size of the scene.
27 | public static func image(precision: Float = 1, size: CGSize) -> Snapshotting {
28 | return .skScene(precision: precision, size: size)
29 | }
30 | }
31 | #endif
32 |
33 | fileprivate extension Snapshotting where Value == SKScene, Format == Image {
34 | static func skScene(precision: Float, size: CGSize) -> Snapshotting {
35 | return Snapshotting.image(precision: precision).pullback { scene in
36 | let view = SKView(frame: .init(x: 0, y: 0, width: size.width, height: size.height))
37 | view.presentScene(scene)
38 | return view
39 | }
40 | }
41 | }
42 | #endif
43 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/String.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import XCTest
3 |
4 | extension Snapshotting where Value == String, Format == String {
5 | /// A snapshot strategy for comparing strings based on equality.
6 | public static let lines = Snapshotting(pathExtension: "txt", diffing: .lines)
7 | }
8 |
9 | extension Diffing where Value == String {
10 | /// A line-diffing strategy for UTF-8 text.
11 | public static let lines = Diffing(
12 | toData: { Data($0.utf8) },
13 | fromData: { String(decoding: $0, as: UTF8.self) }
14 | ) { old, new in
15 | guard old != new else { return nil }
16 | let hunks = chunk(diff: SnapshotTesting.diff(
17 | old.split(separator: "\n", omittingEmptySubsequences: false).map(String.init),
18 | new.split(separator: "\n", omittingEmptySubsequences: false).map(String.init)
19 | ))
20 | let failure = hunks
21 | .flatMap { [$0.patchMark] + $0.lines }
22 | .joined(separator: "\n")
23 | let attachment = XCTAttachment(data: Data(failure.utf8), uniformTypeIdentifier: "public.patch-file")
24 | return (failure, [attachment])
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SwiftUIView.swift:
--------------------------------------------------------------------------------
1 | #if canImport(SwiftUI)
2 | import Foundation
3 | import SwiftUI
4 |
5 | /// The size constraint for a snapshot (similar to `PreviewLayout`).
6 | public enum SwiftUISnapshotLayout {
7 | #if os(iOS) || os(tvOS)
8 | /// Center the view in a device container described by`config`.
9 | case device(config: ViewImageConfig)
10 | #endif
11 | /// Center the view in a fixed size container.
12 | case fixed(width: CGFloat, height: CGFloat)
13 | /// Fit the view to the ideal size that fits its content.
14 | case sizeThatFits
15 | }
16 |
17 | #if os(iOS) || os(tvOS)
18 | @available(iOS 13.0, tvOS 13.0, *)
19 | extension Snapshotting where Value: SwiftUI.View, Format == UIImage {
20 |
21 | /// A snapshot strategy for comparing SwiftUI Views based on pixel equality.
22 | public static var image: Snapshotting {
23 | return .image()
24 | }
25 |
26 | /// A snapshot strategy for comparing SwiftUI Views based on pixel equality.
27 | ///
28 | /// - Parameters:
29 | /// - drawHierarchyInKeyWindow: Utilize the simulator's key window in order to render `UIAppearance` and `UIVisualEffect`s. This option requires a host application for your tests and will _not_ work for framework test targets.
30 | /// - precision: The percentage of pixels that must match.
31 | /// - size: A view size override.
32 | /// - traits: A trait collection override.
33 | public static func image(
34 | drawHierarchyInKeyWindow: Bool = false,
35 | precision: Float = 1,
36 | layout: SwiftUISnapshotLayout = .sizeThatFits,
37 | traits: UITraitCollection = .init()
38 | )
39 | -> Snapshotting {
40 | let config: ViewImageConfig
41 |
42 | switch layout {
43 | #if os(iOS) || os(tvOS)
44 | case let .device(config: deviceConfig):
45 | config = deviceConfig
46 | #endif
47 | case .sizeThatFits:
48 | config = .init(safeArea: .zero, size: nil, traits: traits)
49 | case let .fixed(width: width, height: height):
50 | let size = CGSize(width: width, height: height)
51 | config = .init(safeArea: .zero, size: size, traits: traits)
52 | }
53 |
54 | return SimplySnapshotting.image(precision: precision, scale: traits.displayScale).asyncPullback { view in
55 | var config = config
56 |
57 | let controller: UIViewController
58 |
59 | if config.size != nil {
60 | controller = UIHostingController.init(
61 | rootView: view
62 | )
63 | } else {
64 | let hostingController = UIHostingController.init(rootView: view)
65 |
66 | let maxSize = CGSize(width: 0.0, height: 0.0)
67 | config.size = hostingController.sizeThatFits(in: maxSize)
68 |
69 | controller = hostingController
70 | }
71 |
72 | return snapshotView(
73 | config: config,
74 | drawHierarchyInKeyWindow: drawHierarchyInKeyWindow,
75 | traits: traits,
76 | view: controller.view,
77 | viewController: controller
78 | )
79 | }
80 | }
81 | }
82 | #endif
83 | #endif
84 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIBezierPath.swift:
--------------------------------------------------------------------------------
1 | #if os(iOS) || os(tvOS)
2 | import UIKit
3 |
4 | extension Snapshotting where Value == UIBezierPath, Format == UIImage {
5 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
6 | public static var image: Snapshotting {
7 | return .image()
8 | }
9 |
10 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
11 | ///
12 | /// - Parameter precision: The percentage of pixels that must match.
13 | public static func image(precision: Float = 1, scale: CGFloat = 1) -> Snapshotting {
14 | return SimplySnapshotting.image(precision: precision, scale: scale).pullback { path in
15 | let bounds = path.bounds
16 | let format: UIGraphicsImageRendererFormat
17 | if #available(iOS 11.0, tvOS 11.0, *) {
18 | format = UIGraphicsImageRendererFormat.preferred()
19 | } else {
20 | format = UIGraphicsImageRendererFormat.default()
21 | }
22 | format.scale = scale
23 | return UIGraphicsImageRenderer(bounds: bounds, format: format).image { ctx in
24 | path.fill()
25 | }
26 | }
27 | }
28 | }
29 |
30 | @available(iOS 11.0, tvOS 11.0, *)
31 | extension Snapshotting where Value == UIBezierPath, Format == String {
32 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
33 | public static var elementsDescription: Snapshotting {
34 | Snapshotting.elementsDescription.pullback { path in path.cgPath }
35 | }
36 |
37 | /// A snapshot strategy for comparing bezier paths based on pixel equality.
38 | ///
39 | /// - Parameter numberFormatter: The number formatter used for formatting points.
40 | public static func elementsDescription(numberFormatter: NumberFormatter) -> Snapshotting {
41 | Snapshotting.elementsDescription(
42 | numberFormatter: numberFormatter
43 | ).pullback { path in path.cgPath }
44 | }
45 | }
46 | #endif
47 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIView.swift:
--------------------------------------------------------------------------------
1 | #if os(iOS) || os(tvOS)
2 | import UIKit
3 |
4 | extension Snapshotting where Value == UIView, Format == UIImage {
5 | /// A snapshot strategy for comparing views based on pixel equality.
6 | public static var image: Snapshotting {
7 | return .image()
8 | }
9 |
10 | /// A snapshot strategy for comparing views based on pixel equality.
11 | ///
12 | /// - Parameters:
13 | /// - drawHierarchyInKeyWindow: Utilize the simulator's key window in order to render `UIAppearance` and `UIVisualEffect`s. This option requires a host application for your tests and will _not_ work for framework test targets.
14 | /// - precision: The percentage of pixels that must match.
15 | /// - size: A view size override.
16 | /// - traits: A trait collection override.
17 | public static func image(
18 | drawHierarchyInKeyWindow: Bool = false,
19 | precision: Float = 1,
20 | size: CGSize? = nil,
21 | traits: UITraitCollection = .init()
22 | )
23 | -> Snapshotting {
24 |
25 | return SimplySnapshotting.image(precision: precision, scale: traits.displayScale).asyncPullback { view in
26 | snapshotView(
27 | config: .init(safeArea: .zero, size: size ?? view.frame.size, traits: .init()),
28 | drawHierarchyInKeyWindow: drawHierarchyInKeyWindow,
29 | traits: traits,
30 | view: view,
31 | viewController: .init()
32 | )
33 | }
34 | }
35 | }
36 |
37 | extension Snapshotting where Value == UIView, Format == String {
38 | /// A snapshot strategy for comparing views based on a recursive description of their properties and hierarchies.
39 | public static var recursiveDescription: Snapshotting {
40 | return Snapshotting.recursiveDescription()
41 | }
42 |
43 | /// A snapshot strategy for comparing views based on a recursive description of their properties and hierarchies.
44 | public static func recursiveDescription(
45 | size: CGSize? = nil,
46 | traits: UITraitCollection = .init()
47 | )
48 | -> Snapshotting {
49 | return SimplySnapshotting.lines.pullback { view in
50 | let dispose = prepareView(
51 | config: .init(safeArea: .zero, size: size ?? view.frame.size, traits: traits),
52 | drawHierarchyInKeyWindow: false,
53 | traits: .init(),
54 | view: view,
55 | viewController: .init()
56 | )
57 | defer { dispose() }
58 | return purgePointers(
59 | view.perform(Selector(("recursiveDescription"))).retain().takeUnretainedValue()
60 | as! String
61 | )
62 | }
63 | }
64 | }
65 | #endif
66 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIViewController.swift:
--------------------------------------------------------------------------------
1 | #if os(iOS) || os(tvOS)
2 | import UIKit
3 |
4 | extension Snapshotting where Value == UIViewController, Format == UIImage {
5 | /// A snapshot strategy for comparing view controller views based on pixel equality.
6 | public static var image: Snapshotting {
7 | return .image()
8 | }
9 |
10 | /// A snapshot strategy for comparing view controller views based on pixel equality.
11 | ///
12 | /// - Parameters:
13 | /// - config: A set of device configuration settings.
14 | /// - precision: The percentage of pixels that must match.
15 | /// - size: A view size override.
16 | /// - traits: A trait collection override.
17 | public static func image(
18 | on config: ViewImageConfig,
19 | precision: Float = 1,
20 | size: CGSize? = nil,
21 | traits: UITraitCollection = .init()
22 | )
23 | -> Snapshotting {
24 |
25 | return SimplySnapshotting.image(precision: precision, scale: traits.displayScale).asyncPullback { viewController in
26 | snapshotView(
27 | config: size.map { .init(safeArea: config.safeArea, size: $0, traits: config.traits) } ?? config,
28 | drawHierarchyInKeyWindow: false,
29 | traits: traits,
30 | view: viewController.view,
31 | viewController: viewController
32 | )
33 | }
34 | }
35 |
36 | /// A snapshot strategy for comparing view controller views based on pixel equality.
37 | ///
38 | /// - Parameters:
39 | /// - drawHierarchyInKeyWindow: Utilize the simulator's key window in order to render `UIAppearance` and `UIVisualEffect`s. This option requires a host application for your tests and will _not_ work for framework test targets.
40 | /// - precision: The percentage of pixels that must match.
41 | /// - size: A view size override.
42 | /// - traits: A trait collection override.
43 | public static func image(
44 | drawHierarchyInKeyWindow: Bool = false,
45 | precision: Float = 1,
46 | size: CGSize? = nil,
47 | traits: UITraitCollection = .init()
48 | )
49 | -> Snapshotting {
50 |
51 | return SimplySnapshotting.image(precision: precision, scale: traits.displayScale).asyncPullback { viewController in
52 | snapshotView(
53 | config: .init(safeArea: .zero, size: size, traits: traits),
54 | drawHierarchyInKeyWindow: drawHierarchyInKeyWindow,
55 | traits: .init(),
56 | view: viewController.view,
57 | viewController: viewController
58 | )
59 | }
60 | }
61 | }
62 |
63 | extension Snapshotting where Value == UIViewController, Format == String {
64 | /// A snapshot strategy for comparing view controllers based on their embedded controller hierarchy.
65 | public static var hierarchy: Snapshotting {
66 | return Snapshotting.lines.pullback { viewController in
67 | let dispose = prepareView(
68 | config: .init(),
69 | drawHierarchyInKeyWindow: false,
70 | traits: .init(),
71 | view: viewController.view,
72 | viewController: viewController
73 | )
74 | defer { dispose() }
75 | return purgePointers(
76 | viewController.perform(Selector(("_printHierarchy"))).retain().takeUnretainedValue() as! String
77 | )
78 | }
79 | }
80 |
81 | /// A snapshot strategy for comparing view controller views based on a recursive description of their properties and hierarchies.
82 | public static var recursiveDescription: Snapshotting {
83 | return Snapshotting.recursiveDescription()
84 | }
85 |
86 | /// A snapshot strategy for comparing view controller views based on a recursive description of their properties and hierarchies.
87 | ///
88 | /// - Parameters:
89 | /// - config: A set of device configuration settings.
90 | /// - size: A view size override.
91 | /// - traits: A trait collection override.
92 | public static func recursiveDescription(
93 | on config: ViewImageConfig = .init(),
94 | size: CGSize? = nil,
95 | traits: UITraitCollection = .init()
96 | )
97 | -> Snapshotting {
98 | return SimplySnapshotting.lines.pullback { viewController in
99 | let dispose = prepareView(
100 | config: .init(safeArea: config.safeArea, size: size ?? config.size, traits: config.traits),
101 | drawHierarchyInKeyWindow: false,
102 | traits: traits,
103 | view: viewController.view,
104 | viewController: viewController
105 | )
106 | defer { dispose() }
107 | return purgePointers(
108 | viewController.view.perform(Selector(("recursiveDescription"))).retain().takeUnretainedValue()
109 | as! String
110 | )
111 | }
112 | }
113 | }
114 | #endif
115 |
--------------------------------------------------------------------------------
/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/URLRequest.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | #if canImport(FoundationNetworking)
3 | import FoundationNetworking
4 | #endif
5 |
6 | extension Snapshotting where Value == URLRequest, Format == String {
7 | /// A snapshot strategy for comparing requests based on raw equality.
8 | public static let raw = Snapshotting.raw(pretty: false)
9 |
10 | /// A snapshot strategy for comparing requests based on raw equality.
11 | ///
12 | /// - Parameter pretty: Attempts to pretty print the body of the request (supports JSON).
13 | public static func raw(pretty: Bool) -> Snapshotting {
14 | return SimplySnapshotting.lines.pullback { (request: URLRequest) in
15 | let method = "\(request.httpMethod ?? "GET") \(request.url?.absoluteString ?? "(null)")"
16 |
17 | let headers = (request.allHTTPHeaderFields ?? [:])
18 | .map { key, value in "\(key): \(value)" }
19 | .sorted()
20 |
21 | let body: [String]
22 | do {
23 | if pretty, #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) {
24 | body = try request.httpBody
25 | .map { try JSONSerialization.jsonObject(with: $0, options: []) }
26 | .map { try JSONSerialization.data(withJSONObject: $0, options: [.prettyPrinted, .sortedKeys]) }
27 | .map { ["\n\(String(decoding: $0, as: UTF8.self))"] }
28 | ?? []
29 | } else {
30 | throw NSError(domain: "co.pointfree.Never", code: 1, userInfo: nil)
31 | }
32 | }
33 | catch {
34 | body = request.httpBody
35 | .map { ["\n\(String(decoding: $0, as: UTF8.self))"] }
36 | ?? []
37 | }
38 |
39 | return ([method] + headers + body).joined(separator: "\n")
40 | }
41 | }
42 |
43 | /// A snapshot strategy for comparing requests based on a cURL representation.
44 | public static let curl = SimplySnapshotting.lines.pullback { (request: URLRequest) in
45 |
46 | var components = ["curl"]
47 |
48 | // HTTP Method
49 | let httpMethod = request.httpMethod!
50 | switch httpMethod {
51 | case "GET": break
52 | case "HEAD": components.append("--head")
53 | default: components.append("--request \(httpMethod)")
54 | }
55 |
56 | // Headers
57 | if let headers = request.allHTTPHeaderFields {
58 | for field in headers.keys.sorted() where field != "Cookie" {
59 | let escapedValue = headers[field]!.replacingOccurrences(of: "\"", with: "\\\"")
60 | components.append("--header \"\(field): \(escapedValue)\"")
61 | }
62 | }
63 |
64 | // Body
65 | if let httpBodyData = request.httpBody, let httpBody = String(data: httpBodyData, encoding: .utf8) {
66 | var escapedBody = httpBody.replacingOccurrences(of: "\\\"", with: "\\\\\"")
67 | escapedBody = escapedBody.replacingOccurrences(of: "\"", with: "\\\"")
68 |
69 | components.append("--data \"\(escapedBody)\"")
70 | }
71 |
72 | // Cookies
73 | if let cookie = request.allHTTPHeaderFields?["Cookie"] {
74 | let escapedValue = cookie.replacingOccurrences(of: "\"", with: "\\\"")
75 | components.append("--cookie \"\(escapedValue)\"")
76 | }
77 |
78 | // URL
79 | components.append("\"\(request.url!.absoluteString)\"")
80 |
81 | return components.joined(separator: " \\\n\t")
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Example/Pods/SwiftConfigurationFiles/.swiftformat:
--------------------------------------------------------------------------------
1 | #
2 | # .swiftformat
3 | # Created by Felix Mau (https://felix.hamburg)
4 | #
5 |
6 | #
7 | # Configuration file for SwiftFormat (https://github.com/nicklockwood/SwiftFormat/)
8 | #
9 | # A more detailed documentation of the rules can be found at
10 | # https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md
11 | #
12 |
13 | #
14 | # File options
15 | #
16 | # Note: Excluded paths are relative to the SwiftFormat configuration file.
17 | # Therefore we can't specify them in the here. Just leaving this commented out line here as a reminder.
18 | #
19 | # --exclude Pods,Generated
20 |
21 | #
22 | # Rules
23 | #
24 | --disable andOperator
25 | --disable blankLinesAtStartOfScope
26 | --disable wrapMultilineStatementBraces
27 |
28 | #
29 | # Rule Configuration
30 | #
31 | --wraparguments "after-first"
32 | --wrapcollections "before-first"
33 | --importgrouping testable-last
--------------------------------------------------------------------------------
/Example/Pods/SwiftConfigurationFiles/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Felix Mau
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/Example/Pods/SwiftConfigurationFiles/README.md:
--------------------------------------------------------------------------------
1 | # Swift Configuration Files 🛠
2 | Repository containing configuration files for Swift development, e.g. for tools like [SwiftLint](https://github.com/realm/SwiftLint) or [SwiftFormat](https://github.com/nicklockwood/SwiftFormat).
3 |
4 | ### Author
5 | Felix Mau (me(@)felix.hamburg)
6 |
7 | ### License
8 |
9 | Swift Configuration Files is available under the MIT license. See the LICENSE file for more info.
--------------------------------------------------------------------------------
/Example/Pods/SwiftFormat/CommandLineTool/swiftformat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Example/Pods/SwiftFormat/CommandLineTool/swiftformat
--------------------------------------------------------------------------------
/Example/Pods/SwiftFormat/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Nick Lockwood
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 |
--------------------------------------------------------------------------------
/Example/Pods/SwiftLint/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Realm Inc.
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 |
--------------------------------------------------------------------------------
/Example/Pods/SwiftLint/swiftlint:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/Example/Pods/SwiftLint/swiftlint
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-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 | 3.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleIdentifier
8 | ${PRODUCT_BUNDLE_IDENTIFIER}
9 | CFBundleInfoDictionaryVersion
10 | 6.0
11 | CFBundleName
12 | ${PRODUCT_NAME}
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks-input-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${PODS_ROOT}/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks.sh
2 | ${BUILT_PRODUCTS_DIR}/GradientLoadingBar/GradientLoadingBar.framework
3 | ${BUILT_PRODUCTS_DIR}/SnapshotTesting/SnapshotTesting.framework
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks-output-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GradientLoadingBar.framework
2 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapshotTesting.framework
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_GradientLoadingBar : NSObject
3 | @end
4 | @implementation PodsDummy_GradientLoadingBar
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double GradientLoadingBarVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char GradientLoadingBarVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
6 | PODS_BUILD_DIR = ${BUILD_DIR}
7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_ROOT = ${SRCROOT}
9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
12 | SKIP_INSTALL = YES
13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
14 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.modulemap:
--------------------------------------------------------------------------------
1 | framework module GradientLoadingBar {
2 | umbrella header "GradientLoadingBar-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
6 | PODS_BUILD_DIR = ${BUILD_DIR}
7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_ROOT = ${SRCROOT}
9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
12 | SKIP_INSTALL = YES
13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
14 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.unit-tests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar" "${PODS_CONFIGURATION_BUILD_DIR}/SnapshotTesting"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
6 | OTHER_LDFLAGS = $(inherited) -ObjC -framework "GradientLoadingBar" -framework "SnapshotTesting" -framework "XCTest"
7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_ROOT = ${SRCROOT}
11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
14 | SKIP_INSTALL = YES
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.unit-tests.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar" "${PODS_CONFIGURATION_BUILD_DIR}/SnapshotTesting"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
6 | OTHER_LDFLAGS = $(inherited) -ObjC -framework "GradientLoadingBar" -framework "SnapshotTesting" -framework "XCTest"
7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_ROOT = ${SRCROOT}
11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
14 | SKIP_INSTALL = YES
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-Example/Pods-Example-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 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## GradientLoadingBar
5 |
6 | Copyright (c) 2017-2020 Felix Mau
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in
16 | all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | THE SOFTWARE.
25 |
26 |
27 | ## SwiftConfigurationFiles
28 |
29 | Copyright (c) 2020 Felix Mau
30 |
31 | Permission is hereby granted, free of charge, to any person obtaining a copy
32 | of this software and associated documentation files (the "Software"), to deal
33 | in the Software without restriction, including without limitation the rights
34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35 | copies of the Software, and to permit persons to whom the Software is
36 | furnished to do so, subject to the following conditions:
37 |
38 | The above copyright notice and this permission notice shall be included in
39 | all copies or substantial portions of the Software.
40 |
41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
47 | THE SOFTWARE.
48 |
49 | ## SwiftFormat
50 |
51 | MIT License
52 |
53 | Copyright (c) 2016 Nick Lockwood
54 |
55 | Permission is hereby granted, free of charge, to any person obtaining a copy
56 | of this software and associated documentation files (the "Software"), to deal
57 | in the Software without restriction, including without limitation the rights
58 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
59 | copies of the Software, and to permit persons to whom the Software is
60 | furnished to do so, subject to the following conditions:
61 |
62 | The above copyright notice and this permission notice shall be included in all
63 | copies or substantial portions of the Software.
64 |
65 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
66 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
67 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
68 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
69 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
70 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
71 | SOFTWARE.
72 |
73 |
74 | ## SwiftLint
75 |
76 | The MIT License (MIT)
77 |
78 | Copyright (c) 2020 Realm Inc.
79 |
80 | Permission is hereby granted, free of charge, to any person obtaining a copy
81 | of this software and associated documentation files (the "Software"), to deal
82 | in the Software without restriction, including without limitation the rights
83 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
84 | copies of the Software, and to permit persons to whom the Software is
85 | furnished to do so, subject to the following conditions:
86 |
87 | The above copyright notice and this permission notice shall be included in all
88 | copies or substantial portions of the Software.
89 |
90 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
91 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
92 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
93 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
94 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
95 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
96 | SOFTWARE.
97 |
98 | Generated by CocoaPods - https://cocoapods.org
99 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_Example : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_Example
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Debug-input-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh
2 | ${BUILT_PRODUCTS_DIR}/GradientLoadingBar/GradientLoadingBar.framework
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Debug-output-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GradientLoadingBar.framework
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Release-input-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh
2 | ${BUILT_PRODUCTS_DIR}/GradientLoadingBar/GradientLoadingBar.framework
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Release-output-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GradientLoadingBar.framework
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-Example/Pods-Example-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_ExampleVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_ExampleVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar"
4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar/GradientLoadingBar.framework/Headers"
6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
8 | OTHER_LDFLAGS = $(inherited) -framework "GradientLoadingBar"
9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
10 | PODS_BUILD_DIR = ${BUILD_DIR}
11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
13 | PODS_ROOT = ${SRCROOT}/Pods
14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-Example/Pods-Example.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_Example {
2 | umbrella header "Pods-Example-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar"
4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar/GradientLoadingBar.framework/Headers"
6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks'
7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
8 | OTHER_LDFLAGS = $(inherited) -framework "GradientLoadingBar"
9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
10 | PODS_BUILD_DIR = ${BUILD_DIR}
11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
13 | PODS_ROOT = ${SRCROOT}/Pods
14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
16 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-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 | 1.9.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_SnapshotTesting : NSObject
3 | @end
4 | @implementation PodsDummy_SnapshotTesting
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double SnapshotTestingVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char SnapshotTestingVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SnapshotTesting
3 | ENABLE_BITCODE = NO
4 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
5 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
6 | LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
7 | OTHER_LDFLAGS = $(inherited) -framework "XCTest"
8 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
9 | PODS_BUILD_DIR = ${BUILD_DIR}
10 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
11 | PODS_ROOT = ${SRCROOT}
12 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SnapshotTesting
13 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
14 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
15 | SKIP_INSTALL = YES
16 | SWIFT_INCLUDE_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib"
17 | SYSTEM_FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
18 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
19 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.modulemap:
--------------------------------------------------------------------------------
1 | framework module SnapshotTesting {
2 | umbrella header "SnapshotTesting-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SnapshotTesting
3 | ENABLE_BITCODE = NO
4 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
5 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
6 | LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
7 | OTHER_LDFLAGS = $(inherited) -framework "XCTest"
8 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
9 | PODS_BUILD_DIR = ${BUILD_DIR}
10 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
11 | PODS_ROOT = ${SRCROOT}
12 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SnapshotTesting
13 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
14 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
15 | SKIP_INSTALL = YES
16 | SWIFT_INCLUDE_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib"
17 | SYSTEM_FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks"
18 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
19 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftConfigurationFiles/SwiftConfigurationFiles.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftConfigurationFiles
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_ROOT = ${SRCROOT}
7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftConfigurationFiles
8 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftConfigurationFiles/SwiftConfigurationFiles.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftConfigurationFiles
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_ROOT = ${SRCROOT}
7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftConfigurationFiles
8 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
6 | PODS_BUILD_DIR = ${BUILD_DIR}
7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_ROOT = ${SRCROOT}
9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftFormat
10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
12 | SKIP_INSTALL = YES
13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
14 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift
5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
6 | PODS_BUILD_DIR = ${BUILD_DIR}
7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_ROOT = ${SRCROOT}
9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftFormat
10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
12 | SKIP_INSTALL = YES
13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
14 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftLint/SwiftLint.debug.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_ROOT = ${SRCROOT}
7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint
8 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/SwiftLint/SwiftLint.release.xcconfig:
--------------------------------------------------------------------------------
1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_ROOT = ${SRCROOT}
7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint
8 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
12 |
--------------------------------------------------------------------------------
/Example/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | # app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app
2 | # apple_id("[[APPLE_ID]]") # Your Apple email address
3 |
4 |
5 | # For more information about the Appfile, see:
6 | # https://docs.fastlane.tools/advanced/#appfile
7 |
--------------------------------------------------------------------------------
/Example/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | # This file contains the fastlane.tools configuration
2 | # You can find the documentation at https://docs.fastlane.tools
3 | #
4 | # For a list of all available actions, check out
5 | #
6 | # https://docs.fastlane.tools/actions
7 | #
8 | # For a list of all available plugins, check out
9 | #
10 | # https://docs.fastlane.tools/plugins/available-plugins
11 | #
12 |
13 | # Uncomment the line if you want fastlane to automatically update itself
14 | # update_fastlane
15 |
16 | default_platform(:ios)
17 |
18 | platform :ios do
19 | desc "Execute SwiftFormat and treat any formatting errors as real errors."
20 | lane :format do
21 | swiftformat(
22 | executable: "Pods/SwiftFormat/CommandLineTool/swiftformat",
23 | config: "Pods/SwiftConfigurationFiles/.swiftformat",
24 | path: "../",
25 | lint: true
26 | )
27 | end
28 |
29 | desc "Execute SwiftLint and treat any formatting errors as real errors."
30 | lane :lint do
31 | swiftlint(
32 | config_file: ".swiftlint.yml",
33 | strict: true,
34 | executable: "Pods/SwiftLint/swiftlint"
35 | )
36 | end
37 |
38 | desc "Verify Carthage support by making sure the scheme `GradientLoadingBar` is shared."
39 | lane :verify_carthage do
40 | #
41 | # For Carthage support we explicitly have to mark the scheme `GradientLoadingBar` as shared.
42 | # Source: https://www.amerhukic.com/how-to-add-carthage-support-in-existing-cocoapod-project
43 | #
44 | build_app(
45 | project: "Pods/Pods.xcodeproj",
46 | scheme: "GradientLoadingBar",
47 | skip_archive: true
48 | )
49 | end
50 |
51 | desc "Execute tests."
52 | lane :tests do
53 | run_tests(
54 | project: "Pods/Pods.xcodeproj",
55 | scheme: "GradientLoadingBar-Unit-Tests",
56 | devices: ["iPhone 14"],
57 | code_coverage: true,
58 | number_of_retries: 1
59 | )
60 | end
61 |
62 | desc "Execute validation of library."
63 | lane :pod_lint do
64 | Dir.chdir("..") do
65 | #
66 | # Move outside of `Example/` directory.
67 | #
68 | # - Note: We skip running the tests here, as we have a separate lane for this which allows better customization.
69 | #
70 | pod_lib_lint(
71 | verbose: true,
72 | skip_tests: true
73 | )
74 | end
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/Example/fastlane/Pluginfile:
--------------------------------------------------------------------------------
1 | # Autogenerated by fastlane
2 | #
3 | # Ensure this file is checked in to source control!
4 |
5 | gem 'fastlane-plugin-swiftformat'
6 |
--------------------------------------------------------------------------------
/GradientLoadingBar.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint GradientLoadingBar.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'GradientLoadingBar'
11 | s.version = '3.0.0'
12 | s.summary = 'A customizable animated gradient loading bar.'
13 |
14 | # This description is used to generate tags and improve search results.
15 | # * Think: What does it do? Why did you write it? What is the focus?
16 | # * Try to keep it short, snappy and to the point.
17 | # * Write the description between the DESC delimiters below.
18 | # * Finally, don't worry about the indent, CocoaPods strips it!
19 |
20 | s.description = <<-DESC
21 | A customizable animated gradient loading bar.
22 | Inspired by https://codepen.io/marcobiedermann/pen/LExXWW
23 | DESC
24 |
25 | s.homepage = 'https://github.com/fxm90/GradientLoadingBar'
26 | s.screenshots = 'https://raw.githubusercontent.com/fxm90/GradientLoadingBar/master/Assets/screen.gif'
27 | s.license = { :type => 'MIT', :file => 'LICENSE' }
28 | s.author = { 'Felix Mau' => 'contact@felix.hamburg' }
29 | s.source = { :git => 'https://github.com/fxm90/GradientLoadingBar.git', :tag => s.version.to_s }
30 |
31 | s.swift_version = '5.5'
32 | s.ios.deployment_target = '13.0'
33 |
34 | s.source_files = 'GradientLoadingBar/Sources/**/*.swift'
35 |
36 | s.test_spec 'Tests' do |test_spec|
37 | test_spec.requires_app_host = true
38 | test_spec.source_files = 'GradientLoadingBar/Tests/**/*.{swift,md}'
39 | test_spec.dependency 'SnapshotTesting', '~> 1.9'
40 | end
41 |
42 | # s.resource_bundles = {
43 | # 'GradientLoadingBar' => ['GradientLoadingBar/Assets/*.png']
44 | # }
45 |
46 | # s.public_header_files = 'Pod/Classes/**/*.h'
47 | # s.frameworks = 'UIKit', 'MapKit'
48 | # s.dependency 'LightweightObservable', '~> 2.1'
49 |
50 | end
51 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Sources/Feature/GradientActivityIndicatorView/GradientActivityIndicatorView+AnimateIsHidden.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientActivityIndicatorView+AnimateIsHidden.swift
3 | // GradientLoadingBar
4 | //
5 | // Created by Felix Mau on 15.12.18.
6 | // Copyright © 2018 Felix Mau. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// Helper methods to fade-in and -out the `GradientActivityIndicatorView` and update the `isHidden` flag
12 | /// accordingly, as the progress-animation is started and stopped based on this flag.
13 | ///
14 | /// - Note: We add these methods as public extensions on `GradientActivityIndicatorView` instead of `UIView`,
15 | /// in order to avoid conflicts with other frameworks.
16 | ///
17 | /// - SeeAlso: [Github Gist – UIView+AnimateIsHidden.swift](https://gist.github.com/fxm90/723b5def31b46035cd92a641e3b184f6)
18 | public extension GradientActivityIndicatorView {
19 |
20 | /// Updates the view visibility.
21 | ///
22 | /// - Parameters:
23 | /// - isHidden: The new view visibility.
24 | /// - duration: The duration of the animation, measured in seconds.
25 | /// - completion: Closure to be executed when the animation sequence ends. This block has no return value and takes a single Boolean
26 | /// argument that indicates whether or not the animations actually finished before the completion handler was called.
27 | ///
28 | /// - SeeAlso: https://developer.apple.com/documentation/uikit/uiview/1622515-animatewithduration
29 | func animate(isHidden: Bool, duration: TimeInterval, completion: ((Bool) -> Void)? = nil) {
30 | if isHidden {
31 | fadeOut(duration: duration,
32 | completion: completion)
33 | } else {
34 | fadeIn(duration: duration,
35 | completion: completion)
36 | }
37 | }
38 |
39 | /// Fade out the current view by animating the `alpha` to zero and update the `isHidden` flag accordingly.
40 | ///
41 | /// - Parameters:
42 | /// - duration: The duration of the animation, measured in seconds.
43 | /// - completion: Closure to be executed when the animation sequence ends. This block has no return value and takes a single Boolean
44 | /// argument that indicates whether or not the animations actually finished before the completion handler was called.
45 | ///
46 | /// - SeeAlso: https://developer.apple.com/documentation/uikit/uiview/1622515-animatewithduration
47 | func fadeOut(duration: TimeInterval = TimeInterval.GradientLoadingBar.fadeOutDuration, completion: ((Bool) -> Void)? = nil) {
48 | UIView.animate(withDuration: duration,
49 | animations: {
50 | self.alpha = 0
51 | },
52 | completion: { isFinished in
53 | // Update `isHidden` flag accordingly:
54 | // - set to `true` in case animation was completely finished.
55 | // - set to `false` in case animation was interrupted, e.g. due to starting of another animation.
56 | self.isHidden = isFinished
57 |
58 | completion?(isFinished)
59 | })
60 | }
61 |
62 | /// Fade in the current view by setting the `isHidden` flag to `false` and animating the `alpha` to one.
63 | ///
64 | /// - Parameters:
65 | /// - duration: The duration of the animation, measured in seconds.
66 | /// - completion: Closure to be executed when the animation sequence ends. This block has no return value and takes a single Boolean
67 | /// argument that indicates whether or not the animations actually finished before the completion handler was called.
68 | ///
69 | /// - SeeAlso: https://developer.apple.com/documentation/uikit/uiview/1622515-animatewithduration
70 | func fadeIn(duration: TimeInterval = TimeInterval.GradientLoadingBar.fadeInDuration, completion: ((Bool) -> Void)? = nil) {
71 | if isHidden {
72 | // Make sure our animation is visible.
73 | isHidden = false
74 | }
75 |
76 | UIView.animate(withDuration: duration,
77 | animations: {
78 | self.alpha = 1
79 | },
80 | completion: completion)
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Sources/Feature/GradientActivityIndicatorView/GradientActivityIndicatorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientActivityIndicatorView.swift
3 | // GradientLoadingBar
4 | //
5 | // Created by Felix Mau on 12.10.16.
6 | // Copyright © 2016 Felix Mau. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import UIKit
11 |
12 | @IBDesignable
13 | open class GradientActivityIndicatorView: UIView {
14 |
15 | // MARK: - Config
16 |
17 | /// Animation-Key for the progress animation.
18 | private enum Config {
19 | static let progressAnimationKey = "GradientActivityIndicatorView--progressAnimation"
20 | }
21 |
22 | // MARK: - Public properties
23 |
24 | /// Boolean flag, whether the view is currently hidden.
25 | override open var isHidden: Bool {
26 | didSet {
27 | viewModel.isHidden = isHidden
28 | }
29 | }
30 |
31 | /// Colors used for the gradient.
32 | public var gradientColors: [UIColor] {
33 | get { viewModel.gradientColors }
34 | set { viewModel.gradientColors = newValue }
35 | }
36 |
37 | /// Duration for the progress animation.
38 | public var progressAnimationDuration: TimeInterval {
39 | get { viewModel.progressAnimationDuration }
40 | set { viewModel.progressAnimationDuration = newValue }
41 | }
42 |
43 | // MARK: - Private properties
44 |
45 | /// The layer holding the gradient.
46 | private let gradientLayer: CAGradientLayer = {
47 | let layer = CAGradientLayer()
48 | layer.anchorPoint = .zero
49 | layer.startPoint = .zero
50 | layer.endPoint = CGPoint(x: 1, y: 0)
51 |
52 | return layer
53 | }()
54 |
55 | /// The progress animation.
56 | ///
57 | /// - Note: `fromValue` and `duration` are updated from the view-model subscription.
58 | private let progressAnimation: CABasicAnimation = {
59 | let animation = CABasicAnimation(keyPath: "position.x")
60 | animation.fromValue = 0
61 | animation.toValue = 0
62 | animation.isRemovedOnCompletion = false
63 | animation.repeatCount = Float.infinity
64 | animation.duration = 0
65 |
66 | return animation
67 | }()
68 |
69 | /// View model containing all logic related to this view.
70 | private let viewModel = GradientActivityIndicatorViewModel()
71 |
72 | /// The dispose bag for the observables.
73 | private var subscriptions = Set()
74 |
75 | // MARK: - Instance Lifecycle
76 |
77 | override public init(frame: CGRect) {
78 | super.init(frame: frame)
79 |
80 | commonInit()
81 | }
82 |
83 | public required init?(coder aDecoder: NSCoder) {
84 | super.init(coder: aDecoder)
85 |
86 | commonInit()
87 | }
88 |
89 | // MARK: - Public methods
90 |
91 | override open func layoutSubviews() {
92 | super.layoutSubviews()
93 |
94 | viewModel.bounds = bounds
95 | }
96 |
97 | override open func point(inside _: CGPoint, with _: UIEvent?) -> Bool {
98 | // Passing all touches to the next view (if any), in the view stack.
99 | false
100 | }
101 |
102 | // MARK: - Private methods
103 |
104 | private func commonInit() {
105 | layer.insertSublayer(gradientLayer, at: 0)
106 | layer.masksToBounds = true
107 |
108 | bindViewModelToView()
109 | }
110 |
111 | private func bindViewModelToView() {
112 | viewModel.isAnimating.sink { [weak self] isAnimating in
113 | self?.updateProgressAnimation(isAnimating: isAnimating)
114 | }.store(in: &subscriptions)
115 |
116 | viewModel.gradientLayerSizeUpdate.sink { [weak self] sizeUpdate in
117 | self?.gradientLayer.frame = sizeUpdate.frame
118 | self?.progressAnimation.fromValue = sizeUpdate.fromValue
119 | }.store(in: &subscriptions)
120 |
121 | viewModel.gradientLayerColors.sink { [weak self] gradientColors in
122 | self?.gradientLayer.colors = gradientColors
123 | }.store(in: &subscriptions)
124 |
125 | viewModel.gradientLayerAnimationDuration.sink { [weak self] animationDuration in
126 | self?.progressAnimation.duration = animationDuration
127 | }.store(in: &subscriptions)
128 | }
129 |
130 | private func updateProgressAnimation(isAnimating: Bool) {
131 | if isAnimating {
132 | gradientLayer.add(progressAnimation, forKey: Config.progressAnimationKey)
133 | } else {
134 | gradientLayer.removeAnimation(forKey: Config.progressAnimationKey)
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Sources/Feature/GradientLoadingBar/GradientLoadingBarController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientLoadingBarController.swift
3 | // GradientLoadingBar
4 | //
5 | // Created by Felix Mau on 12.11.16.
6 | // Copyright © 2016 Felix Mau. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import UIKit
11 |
12 | /// Type-alias for the controller to match pod name.
13 | public typealias GradientLoadingBar = GradientLoadingBarController
14 |
15 | /// The `GradientLoadingBarController` mediates between the `GradientLoadingBarViewModel` and the corresponding `GradientActivityIndicatorView`.
16 | open class GradientLoadingBarController {
17 |
18 | // MARK: - Public properties
19 |
20 | /// The height of the gradient bar.
21 | ///
22 | /// - Note: This property needs to have a public access level to allow overwriting `setupConstraints()`.
23 | public let height: CGFloat
24 |
25 | /// Flag whether the top layout constraint should respect the `safeAreaLayoutGuide`.
26 | ///
27 | /// - Note: This property needs to have a public access level to allow overwriting `setupConstraints()`.
28 | public let isRelativeToSafeArea: Bool
29 |
30 | /// The view containing the gradient layer.
31 | public let gradientActivityIndicatorView = GradientActivityIndicatorView()
32 |
33 | /// The colors for the gradient.
34 | public var gradientColors: [UIColor] {
35 | get { gradientActivityIndicatorView.gradientColors }
36 | set { gradientActivityIndicatorView.gradientColors = newValue }
37 | }
38 |
39 | /// The duration for the progress animation.
40 | public var progressAnimationDuration: TimeInterval {
41 | get { gradientActivityIndicatorView.progressAnimationDuration }
42 | set { gradientActivityIndicatorView.progressAnimationDuration = newValue }
43 | }
44 |
45 | /// Boolean flag, whether the view is currently hidden.
46 | public var isHidden: Bool {
47 | get { gradientActivityIndicatorView.isHidden }
48 | set { gradientActivityIndicatorView.isHidden = newValue }
49 | }
50 |
51 | /// Singleton instance.
52 | public static var shared = GradientLoadingBar()
53 |
54 | // MARK: - Private properties
55 |
56 | /// View model containing logic for the gradient view.
57 | private let viewModel = GradientLoadingBarViewModel()
58 |
59 | /// The dispose bag for the observables.
60 | private var subscriptions = Set()
61 |
62 | // MARK: - Instance Lifecycle
63 |
64 | /// Creates a new gradient loading bar instance.
65 | ///
66 | /// - Parameters:
67 | /// - height: Height of the gradient bar.
68 | /// - isRelativeToSafeArea: Flag whether the top layout constraint should respect the `safeAreaLayoutGuide`.
69 | ///
70 | /// - Returns: Instance of gradient loading bar.
71 | public init(height: CGFloat = .GradientLoadingBar.height, isRelativeToSafeArea: Bool = true) {
72 | self.height = height
73 | self.isRelativeToSafeArea = isRelativeToSafeArea
74 |
75 | bindViewModelToView()
76 |
77 | // We don't want the view to be visible initially.
78 | isHidden = true
79 | }
80 |
81 | deinit {
82 | gradientActivityIndicatorView.removeFromSuperview()
83 | }
84 |
85 | // MARK: - Public methods
86 |
87 | /// Apply layout constraints for gradient loading view.
88 | open func setupConstraints(superview: UIView) {
89 | let superViewTopAnchor: NSLayoutYAxisAnchor
90 | if #available(iOS 11.0, *), isRelativeToSafeArea {
91 | superViewTopAnchor = superview.safeAreaLayoutGuide.topAnchor
92 | } else {
93 | superViewTopAnchor = superview.topAnchor
94 | }
95 |
96 | NSLayoutConstraint.activate([
97 | gradientActivityIndicatorView.topAnchor.constraint(equalTo: superViewTopAnchor),
98 | gradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: height),
99 |
100 | gradientActivityIndicatorView.leadingAnchor.constraint(equalTo: superview.leadingAnchor),
101 | gradientActivityIndicatorView.trailingAnchor.constraint(equalTo: superview.trailingAnchor),
102 | ])
103 | }
104 |
105 | /// Fades in the gradient loading bar.
106 | public func fadeIn(duration: TimeInterval = .GradientLoadingBar.fadeInDuration, completion: ((Bool) -> Void)? = nil) {
107 | gradientActivityIndicatorView.fadeIn(duration: duration,
108 | completion: completion)
109 | }
110 |
111 | /// Fades out the gradient loading bar.
112 | public func fadeOut(duration: TimeInterval = .GradientLoadingBar.fadeOutDuration, completion: ((Bool) -> Void)? = nil) {
113 | gradientActivityIndicatorView.fadeOut(duration: duration,
114 | completion: completion)
115 | }
116 |
117 | // MARK: - Private methods
118 |
119 | private func bindViewModelToView() {
120 | viewModel.superview.sink { [weak self] superview in
121 | self?.updateSuperview(superview)
122 | }.store(in: &subscriptions)
123 | }
124 |
125 | private func updateSuperview(_ superview: UIView?) {
126 | // If the view’s superview is not nil, the superview releases the view.
127 | gradientActivityIndicatorView.removeFromSuperview()
128 |
129 | if let superview = superview {
130 | gradientActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
131 | superview.addSubview(gradientActivityIndicatorView)
132 |
133 | setupConstraints(superview: superview)
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Sources/Feature/GradientLoadingBar/GradientLoadingBarViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientLoadingBarViewModel.swift
3 | // GradientLoadingBar
4 | //
5 | // Created by Felix Mau on 26.12.17.
6 | // Copyright © 2017 Felix Mau. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import UIKit
11 |
12 | /// This view model checks for the availability of the key-window,
13 | /// and adds it as a superview to the gradient-view.
14 | final class GradientLoadingBarViewModel {
15 |
16 | // MARK: - Public properties
17 |
18 | /// Observable for the superview of the gradient-view.
19 | var superview: AnyPublisher {
20 | superviewSubject.eraseToAnyPublisher()
21 | }
22 |
23 | // MARK: - Private properties
24 |
25 | private let superviewSubject = CurrentValueSubject(nil)
26 |
27 | private var subscriptions = Set()
28 |
29 | // MARK: - Instance Lifecycle
30 |
31 | init(sharedApplication: UIApplicationProtocol = UIApplication.shared, notificationCenter: NotificationCenter = .default) {
32 | if let keyWindow = sharedApplication.keyWindowInConnectedScenes {
33 | superviewSubject.value = keyWindow
34 | }
35 |
36 | // The key window might be not available yet. This can happen, if the initializer is called from
37 | // `UIApplicationDelegate.application(_:didFinishLaunchingWithOptions:)`.
38 | // Furthermore the key window can change. Therefore we setup an observer to inform the view model
39 | // when a new `UIWindow` object becomes the key window.
40 | notificationCenter
41 | .publisher(for: UIWindow.didBecomeKeyNotification)
42 | .compactMap { _ in sharedApplication.keyWindowInConnectedScenes }
43 | .sink { [weak self] keyWindow in
44 | self?.superviewSubject.value = keyWindow
45 | }
46 | .store(in: &subscriptions)
47 | }
48 |
49 | deinit {
50 | /// By providing a custom de-initializer we make sure to remove the gradient-view from its superview.
51 | superviewSubject.value = nil
52 | }
53 | }
54 |
55 | // MARK: - Helper
56 |
57 | /// This allows mocking `UIApplication` in tests.
58 | protocol UIApplicationProtocol: AnyObject {
59 | var keyWindowInConnectedScenes: UIWindow? { get }
60 | }
61 |
62 | extension UIApplication: UIApplicationProtocol {
63 | /// Returns the current key window across multiple iOS versions.
64 | var keyWindowInConnectedScenes: UIWindow? {
65 | guard #available(iOS 15.0, *) else {
66 | return windows.first { $0.isKeyWindow }
67 | }
68 |
69 | // Starting from iOS 15.0 we need to use `UIWindowScene.windows` on a relevant window scene instead.
70 | // Source: https://stackoverflow.com/a/58031897
71 | return connectedScenes
72 | .compactMap { $0 as? UIWindowScene }
73 | .flatMap(\.windows)
74 | .first { $0.isKeyWindow }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Sources/Feature/GradientLoadingBarView/GradientLoadingBarView+ViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientLoadingBarView+ViewModel.swift
3 | // GradientLoadingBar
4 | //
5 | // Created by Felix Mau on 21.03.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import SwiftUI
11 |
12 | // See documentation of `GradientLoadingBarView` for further details
13 | // on this compile condition.
14 | #if arch(arm64) || arch(x86_64)
15 |
16 | @available(iOS 15.0, *)
17 | extension GradientLoadingBarView {
18 | /// This view model contains all logic related to the `GradientLoadingBarView`
19 | /// and the corresponding progress animation.
20 | final class ViewModel: ObservableObject {
21 |
22 | // MARK: - Public properties
23 |
24 | /// The gradient colors used for the progress animation (including the reversed colors).
25 | let gradientColors: [Color]
26 |
27 | /// The horizontal offset of the `LinearGradient` used to simulate the progress animation.
28 | @Published
29 | private(set) var horizontalOffset: CGFloat = 0
30 |
31 | /// The current size of the `GradientLoadingBarView`.
32 | @Published
33 | var size: CGSize = .zero {
34 | didSet {
35 | // This will stop any ongoing animation.
36 | // Source: https://stackoverflow.com/a/59150940
37 | withAnimation(.linear(duration: 0)) {
38 | horizontalOffset = -size.width
39 | }
40 |
41 | let progressAnimation: Animation = .linear(duration: progressDuration).repeatForever(autoreverses: false)
42 | withAnimation(progressAnimation) {
43 | horizontalOffset = size.width
44 | }
45 | }
46 | }
47 |
48 | /// The width of the `LinearGradient`.
49 | var gradientWidth: CGFloat {
50 | // To fit `gradientColors + reversedGradientColors + gradientColors` in our view,
51 | // we have to apply three times the width of our parent view.
52 | size.width * 3
53 | }
54 |
55 | // MARK: - Private properties
56 |
57 | private var progressDuration: TimeInterval
58 |
59 | // MARK: - Instance Lifecycle
60 |
61 | init(gradientColors: [Color], progressDuration: TimeInterval) {
62 | // Simulate infinite animation - Therefore we'll reverse the colors and remove the first and last item
63 | // to prevent duplicate values at the "inner edges" destroying the infinite look.
64 | //
65 | // E.g. for array of [.red, .yellow, .green]
66 | // we will create [.red, .yellow, .green, .yellow, .red, .yellow, .green]
67 | //
68 | // E.g. for array of [.red, .yellow, .green, .blue]
69 | // we will create [.red, .yellow, .green, .blue, .green, .yellow, .red, .yellow, .green, .blue]
70 | let reversedGradientColors = gradientColors
71 | .reversed()
72 | .dropFirst()
73 | .dropLast()
74 |
75 | self.gradientColors = gradientColors + reversedGradientColors + gradientColors
76 | self.progressDuration = progressDuration
77 | }
78 | }
79 | }
80 |
81 | #endif
82 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Sources/Feature/GradientLoadingBarView/GradientLoadingBarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientLoadingBarView.swift
3 | // GradientLoadingBar
4 | //
5 | // Created by Felix Mau on 08.03.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | // Workaround to fix `xcodebuild` errors when running `pod lib lint`, like e.g.:
12 | //
13 | // - `xcodebuild`: error: cannot find type 'Color' in scope
14 | // - `xcodebuild`: error: cannot find type 'View' in scope
15 | //
16 | // > This failure occurs because builds with a deployment target earlier than iOS 11 will also build for the armv7 architecture,
17 | // > and there is no armv7 swiftmodule for SwiftUI in the iOS SDK because the OS version in which it was first introduced (iOS 13)
18 | // > does not support armv7 anymore.
19 | //
20 | // Source: https://stackoverflow.com/a/61954608
21 | #if arch(arm64) || arch(x86_64)
22 |
23 | // For some reason the animation looks broken on iOS versions <= 15.0.
24 | @available(iOS 15.0, *)
25 | public struct GradientLoadingBarView: View {
26 |
27 | // MARK: - Config
28 |
29 | public enum Config {
30 | /// The default color palette for the gradient colors.
31 | public static let gradientColors = UIColor.GradientLoadingBar.gradientColors.map(Color.init)
32 |
33 | /// The default duration for the progress animation, measured in seconds.
34 | public static let progressDuration = TimeInterval.GradientLoadingBar.progressDuration
35 | }
36 |
37 | // MARK: - Private properties
38 |
39 | @StateObject
40 | private var viewModel: ViewModel
41 |
42 | // MARK: - Initializer
43 |
44 | public init(gradientColors: [Color] = Config.gradientColors,
45 | progressDuration: TimeInterval = Config.progressDuration) {
46 | // Even though the docs mention that "You don’t call this initializer directly", this seems to be the correct way to set-up a
47 | // `StateObject` with parameters according to "Lessons from the SwiftUI Digital Lounge".
48 | // https://swiftui-lab.com/random-lessons/#data-10
49 | _viewModel = StateObject(
50 | wrappedValue: ViewModel(gradientColors: gradientColors, progressDuration: progressDuration)
51 | )
52 | }
53 |
54 | // MARK: - Render
55 |
56 | public var body: some View {
57 | Color.clear
58 | // We explicitly have to use a `PreferenceKey` here and store the size on a property in order to restart the animation whenever
59 | // the size changes. Using a `GeometryReader` together with the `onAppear(_:)` view-modifier doesn't reflect any size changes.
60 | .modifier(SizeModifier())
61 | .onPreferenceChange(SizePreferenceKey.self) {
62 | viewModel.size = $0
63 | }
64 | // Using an `overlay` here makes sure that the parent view won't change it's frame.
65 | .overlay(
66 | LinearGradient(colors: viewModel.gradientColors, startPoint: .leading, endPoint: .trailing)
67 | .frame(width: viewModel.gradientWidth)
68 | .offset(x: viewModel.horizontalOffset, y: 0)
69 | )
70 | }
71 | }
72 |
73 | // MARK: - Helper
74 |
75 | private struct SizePreferenceKey: PreferenceKey {
76 | static var defaultValue: CGSize = .zero
77 |
78 | static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
79 | value = nextValue()
80 | }
81 | }
82 |
83 | @available(iOS 15.0, *)
84 | private struct SizeModifier: ViewModifier {
85 | func body(content: Content) -> some View {
86 | content.background(
87 | GeometryReader { geometry in
88 | Color.clear.preference(key: SizePreferenceKey.self,
89 | value: geometry.size)
90 | }
91 | )
92 | }
93 | }
94 |
95 | #endif
96 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Sources/Feature/NotchGradientLoadingBar/NotchGradientLoadingBarViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotchGradientLoadingBarViewModel.swift
3 | // GradientLoadingBar
4 | //
5 | // Created by Felix Mau on 03.10.21.
6 | // Copyright © 2021 Felix Mau. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class NotchGradientLoadingBarViewModel {
12 |
13 | // MARK: - Types
14 |
15 | enum NotchDevice {
16 | case iPhoneX
17 | case iPhoneXS
18 | case iPhoneXSMax
19 | case iPhoneXR
20 | case iPhone11
21 | case iPhone11Pro
22 | case iPhone11ProMax
23 | case iPhone12Mini
24 | case iPhone12
25 | case iPhone12Pro
26 | case iPhone12ProMax
27 | case iPhone13Mini
28 | case iPhone13
29 | case iPhone13Pro
30 | case iPhone13ProMax
31 | case iPhone14
32 | case iPhone14Plus
33 | }
34 |
35 | // MARK: - Public properties
36 |
37 | /// The current device if it has a notch / otherwise `nil`.
38 | let notchDevice: NotchDevice?
39 |
40 | // MARK: - Instance Lifecycle
41 |
42 | init(deviceIdentifier: String = UIDevice.identifier) {
43 | notchDevice = NotchDevice(deviceIdentifier: deviceIdentifier)
44 | }
45 | }
46 |
47 | // MARK: - Helper
48 |
49 | private extension NotchGradientLoadingBarViewModel.NotchDevice {
50 |
51 | /// Creates a new instance from a given `deviceIdentifier` (value returned by `UIDevice.identifier`).
52 | ///
53 | /// - Note: This is taken from
54 | init?(deviceIdentifier: String) {
55 | // swiftlint:disable:previous cyclomatic_complexity
56 | switch deviceIdentifier {
57 | case "iPhone10,3", "iPhone10,6":
58 | self = .iPhoneX
59 |
60 | case "iPhone11,2":
61 | self = .iPhoneXS
62 |
63 | case "iPhone11,4", "iPhone11,6":
64 | self = .iPhoneXSMax
65 |
66 | case "iPhone11,8":
67 | self = .iPhoneXR
68 |
69 | case "iPhone12,1":
70 | self = .iPhone11
71 |
72 | case "iPhone12,3":
73 | self = .iPhone11Pro
74 |
75 | case "iPhone12,5":
76 | self = .iPhone11ProMax
77 |
78 | case "iPhone13,1":
79 | self = .iPhone12Mini
80 |
81 | case "iPhone13,2":
82 | self = .iPhone12
83 |
84 | case "iPhone13,3":
85 | self = .iPhone12Pro
86 |
87 | case "iPhone13,4":
88 | self = .iPhone12ProMax
89 |
90 | case "iPhone14,4":
91 | self = .iPhone13Mini
92 |
93 | case "iPhone14,5":
94 | self = .iPhone13
95 |
96 | case "iPhone14,2":
97 | self = .iPhone13Pro
98 |
99 | case "iPhone14,3":
100 | self = .iPhone13ProMax
101 |
102 | case "iPhone14,7":
103 | self = .iPhone14
104 |
105 | case "iPhone14,8":
106 | self = .iPhone14Plus
107 |
108 | default:
109 | return nil
110 | }
111 | }
112 | }
113 |
114 | private extension UIDevice {
115 |
116 | /// Returns the device identifier.
117 | ///
118 | /// Based on:
119 | /// Adapted for Simulator usage based on
120 | static var identifier: String {
121 | #if targetEnvironment(simulator)
122 | return ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? ""
123 | #else
124 | var systemInfo = utsname()
125 | uname(&systemInfo)
126 |
127 | let machineMirror = Mirror(reflecting: systemInfo.machine)
128 | return machineMirror.children.reduce("") { identifier, element in
129 | guard let value = element.value as? Int8, value != 0 else {
130 | return identifier
131 | }
132 |
133 | return identifier + String(UnicodeScalar(UInt8(value)))
134 | }
135 | #endif
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Sources/Helper/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // GradientLoadingBar
4 | //
5 | // Created by Felix Mau on 26.08.19.
6 | // Copyright © 2019 Felix Mau. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public extension UIColor {
12 |
13 | // swiftlint:disable:next missing_docs
14 | enum GradientLoadingBar {
15 | /// The default color palette for the gradient colors.
16 | ///
17 | /// - SeeAlso: https://codepen.io/marcobiedermann/pen/LExXWW
18 | public static let gradientColors = [
19 | #colorLiteral(red: 0.2980392157, green: 0.8509803922, blue: 0.3921568627, alpha: 1), #colorLiteral(red: 0.3529411765, green: 0.7843137255, blue: 0.9803921569, alpha: 1), #colorLiteral(red: 0, green: 0.4784313725, blue: 1, alpha: 1), #colorLiteral(red: 0.2039215686, green: 0.6666666667, blue: 0.862745098, alpha: 1), #colorLiteral(red: 0.3450980392, green: 0.337254902, blue: 0.8392156863, alpha: 1), #colorLiteral(red: 1, green: 0.1764705882, blue: 0.3333333333, alpha: 1),
20 | ]
21 | }
22 | }
23 |
24 | public extension CGFloat {
25 |
26 | // swiftlint:disable:next missing_docs
27 | enum GradientLoadingBar {
28 | /// The default height of the `GradientLoadingBar`.
29 | public static let height: CGFloat = 3
30 | }
31 | }
32 |
33 | public extension TimeInterval {
34 |
35 | // swiftlint:disable:next missing_docs
36 | enum GradientLoadingBar {
37 | /// The default duration for fading-in the loading bar, measured in seconds.
38 | public static let fadeInDuration = 0.33
39 |
40 | /// The default duration for fading-out the loading bar, measured in seconds.
41 | public static let fadeOutDuration = 0.66
42 |
43 | /// The default duration for the progress animation, measured in seconds.
44 | public static let progressDuration = 3.33
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/GradientActivityIndicatorViewTestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientActivityIndicatorViewTestCase.swift
3 | // ExampleSnapshotTests
4 | //
5 | // Created by Felix Mau on 09.11.19.
6 | // Copyright © 2019 Felix Mau. All rights reserved.
7 | //
8 |
9 | import SnapshotTesting
10 | import XCTest
11 |
12 | @testable import GradientLoadingBar
13 |
14 | final class GradientActivityIndicatorViewTestCase: XCTestCase {
15 |
16 | // MARK: - Config
17 |
18 | private enum Config {
19 | /// The frame we use for rendering the `GradientActivityIndicatorView`.
20 | /// This will also be the image size for our snapshot.
21 | static let frame = CGRect(origin: .zero, size: CGSize(width: 375, height: 4))
22 |
23 | /// The custom colors we use on this test-case.
24 | /// Source: https://color.adobe.com/Pink-Flamingo-color-theme-10343714/
25 | static let gradientColors = [
26 | #colorLiteral(red: 0.9490196078, green: 0.3215686275, blue: 0.431372549, alpha: 1), #colorLiteral(red: 0.9450980392, green: 0.4784313725, blue: 0.5921568627, alpha: 1), #colorLiteral(red: 0.9529411765, green: 0.737254902, blue: 0.7843137255, alpha: 1), #colorLiteral(red: 0.4274509804, green: 0.8666666667, blue: 0.9490196078, alpha: 1), #colorLiteral(red: 0.7568627451, green: 0.9411764706, blue: 0.9568627451, alpha: 1),
27 | ]
28 | }
29 |
30 | // MARK: - Test cases
31 |
32 | func test_gradientActivityIndicatorView_shouldContainCorrectDefaultColors() {
33 | // Given
34 | let gradientActivityIndicatorView = GradientActivityIndicatorView(frame: Config.frame)
35 |
36 | // We're only interested in the initial state containing the correct gradient-colors.
37 | // Therefore we're gonna remove the progress animation.
38 | gradientActivityIndicatorView.layer.removeAllAnimations()
39 |
40 | // Then
41 | assertSnapshot(matching: gradientActivityIndicatorView, as: .image)
42 | }
43 |
44 | func test_gradientActivityIndicatorView_shouldContainCorrectCustomColors() {
45 | // Given
46 | let gradientActivityIndicatorView = GradientActivityIndicatorView(frame: Config.frame)
47 | gradientActivityIndicatorView.gradientColors = Config.gradientColors
48 |
49 | // We're only interested in the initial state containing the correct gradient-colors.
50 | // Therefore we're gonna remove the progress animation.
51 | gradientActivityIndicatorView.layer.removeAllAnimations()
52 |
53 | // Then
54 | assertSnapshot(matching: gradientActivityIndicatorView, as: .image)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/__Snapshots__/GradientActivityIndicatorViewTestCase/test_gradientActivityIndicatorView_shouldContainCorrectCustomColors.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/__Snapshots__/GradientActivityIndicatorViewTestCase/test_gradientActivityIndicatorView_shouldContainCorrectCustomColors.1.png
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/__Snapshots__/GradientActivityIndicatorViewTestCase/test_gradientActivityIndicatorView_shouldContainCorrectDefaultColors.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/__Snapshots__/GradientActivityIndicatorViewTestCase/test_gradientActivityIndicatorView_shouldContainCorrectDefaultColors.1.png
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBar/GradientLoadingBarControllerTestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientLoadingBarControllerTestCase.swift
3 | // ExampleSnapshotTests
4 | //
5 | // Created by Felix Mau on 14.06.20.
6 | // Copyright © 2020 Felix Mau. All rights reserved.
7 | //
8 |
9 | import SnapshotTesting
10 | import XCTest
11 |
12 | @testable import GradientLoadingBar
13 |
14 | final class GradientLoadingBarControllerTestCase: XCTestCase {
15 |
16 | // MARK: - Private properties
17 |
18 | private var window: UIWindow!
19 |
20 | // MARK: - Public methods
21 |
22 | override func setUp() {
23 | super.setUp()
24 |
25 | window = UIApplication.shared.keyWindowInConnectedScenes
26 | }
27 |
28 | override func tearDown() {
29 | window = nil
30 |
31 | super.tearDown()
32 | }
33 |
34 | // MARK: - Test cases
35 |
36 | func test_gradientLoadingBarController() {
37 | // Given
38 | // Show an empty view controller behind loading bar in our test.
39 | let rootViewController = UIViewController()
40 | window.rootViewController = rootViewController
41 |
42 | let expectation = expectation(description: "Expect view to be completely visible.")
43 |
44 | // When
45 | let gradientLoadingBarController = GradientLoadingBarController()
46 | gradientLoadingBarController.fadeIn(duration: 0) { _ in
47 | expectation.fulfill()
48 | }
49 |
50 | // Then
51 | wait(for: [expectation], timeout: 0.1)
52 |
53 | // Workaround, as snapshotting a `UIWindow` is currently not supported and crashes when running all tests.
54 | assertSnapshot(matching: window.layer, as: .image)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBar/__Snapshots__/GradientLoadingBarControllerTestCase/test_gradientLoadingBarController.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBar/__Snapshots__/GradientLoadingBarControllerTestCase/test_gradientLoadingBarController.1.png
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/GradientLoadingBarViewTestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientLoadingBarViewTestCase.swift
3 | // ExampleSnapshotTests
4 | //
5 | // Created by Felix Mau on 10.03.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import SnapshotTesting
10 | import SwiftUI
11 | import XCTest
12 |
13 | @testable import GradientLoadingBar
14 |
15 | @available(iOS 15.0, *)
16 | final class GradientLoadingBarViewTestCase: XCTestCase {
17 |
18 | // MARK: - Config
19 |
20 | private enum Config {
21 | /// The frame we use for rendering the `GradientLoadingBarView`.
22 | /// This will also be the image size for our snapshot.
23 | static let frame = CGRect(origin: .zero, size: CGSize(width: 375, height: 4))
24 |
25 | /// The custom colors we use on this test-case.
26 | /// Source: https://color.adobe.com/Pink-Flamingo-color-theme-10343714/
27 | static let gradientColors = [
28 | #colorLiteral(red: 0.9490196078, green: 0.3215686275, blue: 0.431372549, alpha: 1), #colorLiteral(red: 0.9450980392, green: 0.4784313725, blue: 0.5921568627, alpha: 1), #colorLiteral(red: 0.9529411765, green: 0.737254902, blue: 0.7843137255, alpha: 1), #colorLiteral(red: 0.4274509804, green: 0.8666666667, blue: 0.9490196078, alpha: 1), #colorLiteral(red: 0.7568627451, green: 0.9411764706, blue: 0.9568627451, alpha: 1),
29 | ].map(Color.init)
30 |
31 | /// The percentage of pixels that must match.
32 | static let precision: Float = 0.98
33 | }
34 |
35 | // MARK: - Test cases
36 |
37 | func test_gradientLoadingBarView_shouldContainCorrectDefaultColors() {
38 | // Given
39 | let gradientLoadingBarView = GradientLoadingBarView()
40 | .frame(width: Config.frame.width, height: Config.frame.height)
41 |
42 | // Then
43 | assertSnapshot(matching: gradientLoadingBarView, as: .image(precision: Config.precision))
44 | }
45 |
46 | func test_gradientLoadingBarView_shouldContainCorrectCustomColors() {
47 | // Given
48 | let gradientLoadingBarView = GradientLoadingBarView(gradientColors: Config.gradientColors)
49 | .frame(width: Config.frame.width, height: Config.frame.height)
50 |
51 | // Then
52 | assertSnapshot(matching: gradientLoadingBarView, as: .image(precision: Config.precision))
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/__Snapshots__/GradientLoadingBarViewTestCase/test_gradientLoadingBarView_shouldContainCorrectCustomColors.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/__Snapshots__/GradientLoadingBarViewTestCase/test_gradientLoadingBarView_shouldContainCorrectCustomColors.1.png
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/__Snapshots__/GradientLoadingBarViewTestCase/test_gradientLoadingBarView_shouldContainCorrectDefaultColors.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/__Snapshots__/GradientLoadingBarViewTestCase/test_gradientLoadingBarView_shouldContainCorrectDefaultColors.1.png
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/SnapshotTests/NotchGradientLoadingBar/NotchGradientLoadingBarControllerTestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotchGradientLoadingBarControllerTestCase.swift
3 | // ExampleSnapshotTests
4 | //
5 | // Created by Felix Mau on 14.06.20.
6 | // Copyright © 2020 Felix Mau. All rights reserved.
7 | //
8 |
9 | import SnapshotTesting
10 | import XCTest
11 |
12 | @testable import GradientLoadingBar
13 |
14 | final class NotchGradientLoadingBarControllerTestCase: XCTestCase {
15 | // swiftlint:disable:previous type_name
16 |
17 | // MARK: - Private properties
18 |
19 | private var window: UIWindow!
20 |
21 | // MARK: - Public methods
22 |
23 | override func setUp() {
24 | super.setUp()
25 |
26 | window = UIApplication.shared.keyWindowInConnectedScenes
27 | }
28 |
29 | override func tearDown() {
30 | window = nil
31 |
32 | super.tearDown()
33 | }
34 |
35 | // MARK: - Test cases
36 |
37 | func test_notchGradientLoadingBarController() {
38 | // Given
39 | // Show an empty view controller behind loading bar in our test.
40 | let rootViewController = UIViewController()
41 | window.rootViewController = rootViewController
42 |
43 | let expectation = expectation(description: "Expect view to be completely visible.")
44 |
45 | // When
46 | let gradientLoadingBarController = NotchGradientLoadingBarController()
47 | gradientLoadingBarController.fadeIn(duration: 0) { _ in
48 | expectation.fulfill()
49 | }
50 |
51 | // Then
52 | wait(for: [expectation], timeout: 0.1)
53 |
54 | // Workaround, as snapshotting a `UIWindow` is currently not supported and crashes when running all tests.
55 | assertSnapshot(matching: window.layer, as: .image)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/SnapshotTests/NotchGradientLoadingBar/__Snapshots__/NotchGradientLoadingBarControllerTestCase/test_notchGradientLoadingBarController.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fxm90/GradientLoadingBar/4455b1125eaeda49e89a9c44d5da7c354a69ab6f/GradientLoadingBar/Tests/SnapshotTests/NotchGradientLoadingBar/__Snapshots__/NotchGradientLoadingBarControllerTestCase/test_notchGradientLoadingBarController.1.png
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/SnapshotTests/README.md:
--------------------------------------------------------------------------------
1 | # Snapshot-Tests
2 |
3 | ## Device
4 | Run test-cases with the `iPhone 14` using `iOS 16`.
5 |
6 | - Note: We explicitly use this iPhone to verify the `NotchGradientLoadingBar`.
7 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/UnitTests/GradientActivityIndicatorView/GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift
3 | // ExampleTests
4 | //
5 | // Created by Felix Mau on 18.08.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | @testable import GradientLoadingBar
12 |
13 | // swiftlint:disable:next type_name
14 | final class GradientActivityIndicatorViewModel_SizeUpdateTestCase: XCTestCase {
15 |
16 | // MARK: - Test property `frame`
17 |
18 | func test_frame() {
19 | // Given
20 | let bounds = CGRect(x: 0,
21 | y: 0,
22 | width: .random(in: 0 ... .max),
23 | height: .random(in: 0 ... .max))
24 |
25 | // When
26 | let sizeUpdate = GradientActivityIndicatorViewModel.SizeUpdate(bounds: bounds)
27 |
28 | // Then
29 | let expectedFrame = CGRect(x: 0,
30 | y: 0,
31 | width: bounds.width * 3,
32 | height: bounds.height)
33 |
34 | XCTAssertEqual(sizeUpdate.frame, expectedFrame)
35 | }
36 |
37 | // MARK: - Test property `fromValue`
38 |
39 | func test_fromValue() {
40 | // Given
41 | let bounds = CGRect(x: 0,
42 | y: 0,
43 | width: .random(in: 0 ... .max),
44 | height: .random(in: 0 ... .max))
45 |
46 | // When
47 | let sizeUpdate = GradientActivityIndicatorViewModel.SizeUpdate(bounds: bounds)
48 |
49 | // Then
50 | let expectedFromValue = bounds.width * -2
51 | XCTAssertEqual(sizeUpdate.fromValue, expectedFromValue)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/UnitTests/GradientLoadingBar/GradientLoadingBarViewModelTestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientLoadingBarViewModelTestCase.swift
3 | // ExampleTests
4 | //
5 | // Created by Felix Mau on 26.12.17.
6 | // Copyright © 2017 Felix Mau. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import XCTest
11 |
12 | @testable import GradientLoadingBar
13 |
14 | final class GradientLoadingBarViewModelTestCase: XCTestCase {
15 |
16 | // MARK: - Private properties
17 |
18 | private var sharedApplicationMock: SharedApplicationMock!
19 | private var notificationCenter: NotificationCenter!
20 |
21 | // MARK: - Public methods
22 |
23 | override func setUp() {
24 | super.setUp()
25 |
26 | sharedApplicationMock = SharedApplicationMock()
27 | notificationCenter = NotificationCenter()
28 | }
29 |
30 | override func tearDown() {
31 | notificationCenter = nil
32 | sharedApplicationMock = nil
33 |
34 | super.tearDown()
35 | }
36 |
37 | // MARK: - Test observable `superview`
38 |
39 | func test_init_shouldSetupSuperviewObservable_withNil() throws {
40 | // When
41 | let viewModel = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock,
42 | notificationCenter: notificationCenter)
43 |
44 | var receivedValues = [UIView?]()
45 | let cancellable = viewModel.superview.sink {
46 | receivedValues.append($0)
47 | }
48 |
49 | // Then
50 | withExtendedLifetime(cancellable) {
51 | XCTAssertEqual(receivedValues, [nil])
52 | }
53 | }
54 |
55 | func test_init_shouldSetupSuperviewObservable_withKeyWindow() throws {
56 | // Given
57 | let keyWindow = UIWindow()
58 | sharedApplicationMock.keyWindowInConnectedScenes = keyWindow
59 |
60 | // When
61 | let viewModel = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock,
62 | notificationCenter: notificationCenter)
63 |
64 | // Then
65 | var receivedValues = [UIView?]()
66 | let cancellable = viewModel.superview.sink {
67 | receivedValues.append($0)
68 | }
69 |
70 | // Then
71 | withExtendedLifetime(cancellable) {
72 | XCTAssertEqual(receivedValues, [keyWindow])
73 | }
74 | }
75 |
76 | func test_init_shouldSetupSuperviewObservable_afterUIWindowDidBecomeKeyNotification() {
77 | // Given
78 | let viewModel = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock,
79 | notificationCenter: notificationCenter)
80 |
81 | var receivedKeyWindows = [UIView?]()
82 | let cancellable = viewModel.superview.sink { keyWindow in
83 | receivedKeyWindows.append(keyWindow)
84 | }
85 |
86 | let keyWindow = UIWindow()
87 | sharedApplicationMock.keyWindowInConnectedScenes = keyWindow
88 |
89 | // When
90 | withExtendedLifetime(cancellable) {
91 | notificationCenter.post(name: UIWindow.didBecomeKeyNotification, object: nil)
92 | }
93 |
94 | // Then
95 | let expectedKeyWindows = [nil, keyWindow]
96 | XCTAssertEqual(receivedKeyWindows, expectedKeyWindows)
97 | }
98 |
99 | func test_deinit_shouldResetSuperviewObservable_withNil() {
100 | // Given
101 | let keyWindow = UIWindow()
102 | sharedApplicationMock.keyWindowInConnectedScenes = keyWindow
103 |
104 | var viewModel: GradientLoadingBarViewModel? = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock,
105 | notificationCenter: notificationCenter)
106 |
107 | var receivedKeyWindows = [UIView?]()
108 | let cancellable = viewModel?.superview.sink { keyWindow in
109 | receivedKeyWindows.append(keyWindow)
110 | }
111 |
112 | // When
113 | withExtendedLifetime(cancellable) {
114 | viewModel = nil
115 | }
116 |
117 | // Then
118 | let expectedKeyWindows = [keyWindow, nil]
119 | XCTAssertEqual(receivedKeyWindows, expectedKeyWindows)
120 | }
121 | }
122 |
123 | // MARK: - Mocks
124 |
125 | private final class SharedApplicationMock: UIApplicationProtocol {
126 | var keyWindowInConnectedScenes: UIWindow?
127 | }
128 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/UnitTests/GradientLoadingBarView/GradientLoadingBarView+ViewModelTestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientLoadingBarView+ViewModelTestCase.swift
3 | // ExampleTests
4 | //
5 | // Created by Felix Mau on 22.03.22.
6 | // Copyright © 2022 Felix Mau. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 | import XCTest
11 |
12 | @testable import GradientLoadingBar
13 |
14 | @available(iOS 15.0, *)
15 | final class GradientLoadingBarView_ViewModelTestCase: XCTestCase {
16 | // swiftlint:disable:previous type_name
17 |
18 | // MARK: - Test property `gradientColors`
19 |
20 | func test_initialGradientColors_shouldIncludeReversedGradientColors() {
21 | // Given
22 | let gradientColors: [Color] = [.red, .yellow, .green]
23 |
24 | // When
25 | let viewModel = GradientLoadingBarView.ViewModel(gradientColors: gradientColors, progressDuration: 1)
26 |
27 | // Then
28 | let expectedGradientColors: [Color] = [.red, .yellow, .green, .yellow, .red, .yellow, .green]
29 | XCTAssertEqual(viewModel.gradientColors, expectedGradientColors)
30 | }
31 |
32 | // MARK: - Test property `horizontalOffset`
33 |
34 | func test_initialHorizontalOffset_shouldBeZero() {
35 | // When
36 | let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1)
37 |
38 | // Then
39 | XCTAssertEqual(viewModel.horizontalOffset, 0)
40 | }
41 |
42 | func test_settingSize_shouldUpdateHorizontalOffset() {
43 | // Given
44 | let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1)
45 | let size = CGSize(width: .random(in: 1 ... 100_000), height: .random(in: 1 ... 100_000))
46 |
47 | var receivedValues = [CGFloat]()
48 | let subscription = viewModel.$horizontalOffset.sink {
49 | receivedValues.append($0)
50 | }
51 |
52 | withExtendedLifetime(subscription) {
53 | // When
54 | viewModel.size = size
55 | }
56 |
57 | // Then
58 | let expectedValues = [0, -size.width, size.width]
59 | XCTAssertEqual(receivedValues, expectedValues)
60 | }
61 |
62 | // MARK: - Test property `gradientWidth`
63 |
64 | func test_initialGradientWidth_shouldBeZero() {
65 | // When
66 | let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1)
67 |
68 | // Then
69 | XCTAssertEqual(viewModel.gradientWidth, 0)
70 | }
71 |
72 | func test_settingSize_shouldUpdateGradientWidth() {
73 | // Given
74 | let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1)
75 | let size = CGSize(width: .random(in: 1 ... 100_000), height: .random(in: 1 ... 100_000))
76 |
77 | // When
78 | viewModel.size = size
79 |
80 | // Then
81 | XCTAssertEqual(viewModel.gradientWidth, size.width * 3)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/GradientLoadingBar/Tests/UnitTests/NotchGradientLoadingBar/NotchGradientLoadingBarViewModelTestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotchGradientLoadingBarViewModelTestCase.swift
3 | // ExampleTests
4 | //
5 | // Created by Felix Mau on 26.12.17.
6 | // Copyright © 2017 Felix Mau. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | @testable import GradientLoadingBar
12 |
13 | final class NotchGradientLoadingBarViewModelTestCase: XCTestCase {
14 |
15 | func test_initializer_shouldSetCorrectNotchDevice() {
16 | // Given
17 | let identifiersToNotchDeviceMap: [String: NotchGradientLoadingBarViewModel.NotchDevice] = [
18 | "iPhone10,3": .iPhoneX,
19 | "iPhone10,6": .iPhoneX,
20 | "iPhone11,2": .iPhoneXS,
21 | "iPhone11,4": .iPhoneXSMax,
22 | "iPhone11,6": .iPhoneXSMax,
23 | "iPhone11,8": .iPhoneXR,
24 | "iPhone12,1": .iPhone11,
25 | "iPhone12,3": .iPhone11Pro,
26 | "iPhone12,5": .iPhone11ProMax,
27 | "iPhone13,1": .iPhone12Mini,
28 | "iPhone13,2": .iPhone12,
29 | "iPhone13,3": .iPhone12Pro,
30 | "iPhone13,4": .iPhone12ProMax,
31 | "iPhone14,4": .iPhone13Mini,
32 | "iPhone14,5": .iPhone13,
33 | "iPhone14,2": .iPhone13Pro,
34 | "iPhone14,3": .iPhone13ProMax,
35 | "iPhone14,7": .iPhone14,
36 | "iPhone14,8": .iPhone14Plus,
37 | ]
38 |
39 | identifiersToNotchDeviceMap.forEach { deviceIdentifier, notchDevice in
40 | // When
41 | let viewModel = NotchGradientLoadingBarViewModel(deviceIdentifier: deviceIdentifier)
42 |
43 | // Then
44 | XCTAssertEqual(viewModel.notchDevice, notchDevice)
45 | }
46 | }
47 |
48 | func test_initializer_shouldSetNotchDevice_toNil() {
49 | // Given
50 | let deviceIdentifier = "Foo-Bar-🤡"
51 |
52 | // When
53 | let viewModel = NotchGradientLoadingBarViewModel(deviceIdentifier: deviceIdentifier)
54 |
55 | // Then
56 | XCTAssertNil(viewModel.notchDevice)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017-2020 Felix Mau
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 |
3 | import PackageDescription
4 |
5 | let package = Package(name: "GradientLoadingBar",
6 | platforms: [.iOS(.v13)],
7 | products: [
8 | .library(name: "GradientLoadingBar",
9 | targets: ["GradientLoadingBar"]),
10 | ],
11 | targets: [
12 | .target(name: "GradientLoadingBar",
13 | path: "GradientLoadingBar/Sources"),
14 | .testTarget(name: "GradientLoadingBarTests",
15 | dependencies: ["GradientLoadingBar"],
16 | path: "GradientLoadingBar/Tests/"),
17 | ])
18 |
--------------------------------------------------------------------------------
/_Pods.xcodeproj:
--------------------------------------------------------------------------------
1 | Example/Pods/Pods.xcodeproj
--------------------------------------------------------------------------------