├── .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 --------------------------------------------------------------------------------