diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index b6457ab..0000000 --- a/.codecov.yml +++ /dev/null @@ -1,13 +0,0 @@ -# -# codecov.yml -# Created by Felix Mau (https://felix.hamburg) -# -# How to: -# - https://github.com/codecov/example-swift -# -# Documentation: -# - https://docs.codecov.io/docs/ -# - -ignore: - - "Example" # ignore test coverage of example application diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index f6c84a8..df68c38 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -2,75 +2,61 @@ # continuous-integration.yml # Created by Felix Mau (https://felix.hamburg) # -# Based on: -# - https://docs.github.com/en/actions/quickstart -# - https://futurestud.io/tutorials/github-actions-trigger-builds-on-schedule-cron#schedulegithubactionsusingacron -# - https://about.codecov.io/blog/code-coverage-for-ios-development-using-swift-xcode-and-github-actions/ -# name: Continuous Integration on: - push: pull_request: - schedule: - # Run workflow every day at midnight. - # - # - Note: "Scheduled workflows run on the latest commit on the default or base branch." - # https://docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events - - cron: '0 0 * * *' - -# Use latest Xcode version. -# -# Source: -# - https://www.jessesquires.com/blog/2020/01/06/selecting-an-xcode-version-on-github-ci/ -# - https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md#xcode -env: - DEVELOPER_DIR: /Applications/Xcode_14.0.app/Contents/Developer - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel. + push: + branches: + - main + workflow_dispatch: jobs: - # This workflow starts with a job called "build-and-test". - build-and-test: - name: Build & Test - runs-on: macos-12 - - # Steps represent a sequence of tasks that will be executed as part of the job. + # Lint and Format + # Ensures that the code adheres to the defined style guidelines. + lint-and-format: + runs-on: macos-26 steps: - - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" - - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - - name: Check out repository code - uses: actions/checkout@v2 - - - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - - run: echo "đŸ–„ïž The workflow is now ready to test your code on the runner." + uses: actions/checkout@v6 - - name: Install dependencies - working-directory: ./Example - run: bundle install + - name: Install SwiftFormat via Homebrew + run: brew install swiftformat - - name: Execute SwiftFormat and treat any formatting errors as real errors. - working-directory: ./Example - run: bundle exec fastlane format + - name: Install SwiftLint via Homebrew + run: brew install swiftlint - - name: Execute SwiftLint and treat any formatting errors as real errors. - working-directory: ./Example - run: bundle exec fastlane lint + - name: SwiftFormat + run: make format-check - - name: Execute tests. - working-directory: ./Example - run: bundle exec fastlane tests + - name: SwiftLint + run: make lint - - name: Execute validation Carthage support. - working-directory: ./Example - run: bundle exec fastlane verify_carthage + # Tests + # Ensures that the package compiles and that all tests pass, including code coverage reporting to Codecov. + tests: + runs-on: macos-26 + needs: lint-and-format + steps: + - name: Check out repository code + uses: actions/checkout@v6 - - name: Execute validation of library. - working-directory: ./Example - run: bundle exec fastlane pod_lint + - name: Execute tests + run: make test - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + + # Build Example Application + # Ensures that the package can be integrated into a real application, to catch any potential issues e.g. with access control. + build-example-application: + runs-on: macos-26 + needs: tests + steps: + - name: Check out repository code + uses: actions/checkout@v6 - - run: echo "🍏 This job's status is ${{ job.status }}." + - name: Build Example Application + run: make build-example-application diff --git a/.gitignore b/.gitignore index cb88945..0023a53 100644 --- a/.gitignore +++ b/.gitignore @@ -1,51 +1,8 @@ -# OS X .DS_Store - -# Xcode -build/ -.build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 +/.build +/Packages xcuserdata/ -*.xccheckout -profile -*.moved-aside -DerivedData -*.hmap -*.ipa - -# Bundler -.bundle - -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build - -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control -# -# Note: if you ignore the Pods directory, make sure to uncomment -# `pod install` in .travis.yml -# - -# Include `Pods` in repo in order to make symlink `Pods.xcodeproj` work (used for Carthage support). -# Pods/ - - -# -# Fastlane specific -# Based on: https://docs.fastlane.tools/best-practices/source-control/ -# -**/fastlane/report.xml -**/fastlane/Preview.html -**/fastlane/screenshots -**/fastlane/test_output -**/fastlane/README.md +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/.swift-version b/.swift-version index 9ad974f..0cda48a 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -5.5 +6.2 diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..53bd449 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,23 @@ +# +# .swiftformat +# Configuration file for SwiftFormat (https://github.com/nicklockwood/SwiftFormat/) +# +# A more detailed documentation of the rules can be found at +# https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md +# +# Created by Felix Mau (https://felix.hamburg) +# + +# +# Rules +# + +# Allows an empty line before the first `// MARK: -` statement. +--disable blankLinesAtStartOfScope + +# +# Rule Configuration +# +--indent 2 +--import-grouping testable-last +--wraparguments before-first diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..b278c48 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,85 @@ +# +# .swiftlint.yml +# Configuration file for SwiftLint (https://github.com/realm/SwiftLint/) +# +# A more detailed documentation of the rules can be found at +# https://realm.github.io/SwiftLint/rule-directory.html +# +# Created by Felix Mau (https://felix.hamburg) +# + +included: + - ./ + +excluded: + - ".build/**/*" + - Package.swift + +opt_in_rules: + - accessibility_label_for_image # Images that provide context should have an accessibility label or should be explicitly hidden from accessibility + - accessibility_trait_for_button # All views with tap gestures added should include the .isButton or the .isLink accessibility traits + - closure_spacing # Closure expressions should have a single space inside each brace. + - contains_over_filter_count # Prefer `contains` over comparing `filter(where:).count` to 0. + - contains_over_filter_is_empty # Prefer `contains` over using `filter(where:).isEmpty`. + - contains_over_first_not_nil # Prefer `contains` over `first(where:) != nil` and `firstIndex(where:) != nil`. + - contains_over_range_nil_comparison # Prefer `contains` over `range(of:) != nil` and `range(of:) == nil`. + - convenience_type # Types used for hosting only static members should be implemented as a caseless enum to avoid instantiation. + - duplicate_imports # Imports should be unique. + - empty_collection_literal # Prefer checking `isEmpty` over comparing collection to an empty array or dictionary literal. + - empty_count # Prefer checking `isEmpty` over comparing count to zero. + - empty_parameters # Prefer `() ->` over `Void ->`. + - empty_string # Prefer checking `isEmpty` over comparing string to an empty string literal. + - empty_xctest_method # Empty XCTest method should be avoided. + - explicit_init # Explicitly calling .init() should be avoided. + - fatal_error_message # A fatalError call should have a message. + - file_header # Header comments should be consistent with project patterns. + - first_where # Prefer using `.first(where:)` over `.filter { }.first` in collections. + - for_where # `where` clauses are preferred over a single `if` inside a `for`. + - force_unwrapping # Force unwrapping should be avoided. + - identical_operands # Comparing two identical operands is likely a mistake. + - implicit_return # Prefer implicit returns in closures, functions and getters. + - is_disjoint # Prefer using `Set.isDisjoint(with:)` over `Set.intersection(_:).isEmpty`. + - last_where # Prefer using `.last(where:)` over `.filter { }.last` in collections. + - legacy_multiple # Prefer using the `isMultiple(of:)` function instead of using the remainder operator (`%`). + - missing_docs # Declarations should be documented. + - modifier_order # Modifier order should be consistent. + - multiline_arguments # Arguments should be either on the same line, or one per line. + - multiline_parameters # Functions and methods parameters should be either on the same line, or one per line. + - operator_usage_whitespace # Operators should be surrounded by a single whitespace when they are being used. + - overridden_super_call # Some overridden methods should always call super + - prefer_self_type_over_type_of_self # Prefer `Self` over `type(of: self)` when accessing properties or calling methods. + - prefer_zero_over_explicit_init # Prefer `.zero` over explicit init with zero parameters (e.g. `CGPoint(x: 0, y: 0)`). + - prohibited_super_call # Some methods should not call super, e.g. `UIViewController.loadView()`. + - redundant_nil_coalescing # nil coalescing operator is only evaluated if the lhs is nil, coalescing operator with nil as rhs is redundant. + - toggle_bool # Prefer `someBool.toggle()` over `someBool = !someBool`. + - unavailable_function # Unimplemented functions should be marked as unavailable. + - unused_optional_binding # Prefer `!= nil` over `let _ =` + - void_return # Prefer `-> Void` over `-> ()`. + - weak_delegate # Delegates should be weak to avoid reference cycles + - yoda_condition # The constant literal should be placed on the right-hand side of the comparison operator. + +file_header: + required_pattern: | + \/\/ + \/\/ SWIFTLINT_CURRENT_FILENAME + \/\/ (GradientLoadingBar|GradientLoadingBarTests|GradientLoadingBarExample) + \/\/ + \/\/ Created by .*? on \d{2}\.\d{2}\.\d{2}\. + \/\/ Copyright © \d{4} .*?\. All rights reserved\. + \/\/ + +line_length: 150 + +nesting: + type_level: + warning: 3 + +trailing_comma: + mandatory_comma: true + +custom_rules: + comments_space: + name: "Space After Comment" + regex: '(^ *//\w+)' + message: "There should be a space after //." + severity: warning diff --git a/Assets/advanced-example--thumbnail.png b/Assets/advanced-example--thumbnail.png deleted file mode 100644 index bc8d17c..0000000 Binary files a/Assets/advanced-example--thumbnail.png and /dev/null differ diff --git a/Assets/advanced-example.png b/Assets/advanced-example.png deleted file mode 100644 index e463a11..0000000 Binary files a/Assets/advanced-example.png and /dev/null differ diff --git a/Assets/basic-example--thumbnail.png b/Assets/basic-example--thumbnail.png deleted file mode 100644 index daaded8..0000000 Binary files a/Assets/basic-example--thumbnail.png and /dev/null differ diff --git a/Assets/basic-example.png b/Assets/basic-example.png deleted file mode 100644 index 50a75a6..0000000 Binary files a/Assets/basic-example.png and /dev/null differ diff --git a/Assets/ignoring-safe-area-thumbnail.png b/Assets/ignoring-safe-area-thumbnail.png new file mode 100644 index 0000000..382418d Binary files /dev/null and b/Assets/ignoring-safe-area-thumbnail.png differ diff --git a/Assets/ignoring-safe-area.mov b/Assets/ignoring-safe-area.mov new file mode 100644 index 0000000..c9652d3 Binary files /dev/null and b/Assets/ignoring-safe-area.mov differ diff --git a/Assets/ignoring-safe-area.png b/Assets/ignoring-safe-area.png new file mode 100644 index 0000000..f3da81d Binary files /dev/null and b/Assets/ignoring-safe-area.png differ diff --git a/Assets/navigation-bar-example--thumbnail.png b/Assets/navigation-bar-example--thumbnail.png deleted file mode 100644 index 65590f2..0000000 Binary files a/Assets/navigation-bar-example--thumbnail.png and /dev/null differ diff --git a/Assets/navigation-bar-example.png b/Assets/navigation-bar-example.png deleted file mode 100644 index fd5ff63..0000000 Binary files a/Assets/navigation-bar-example.png and /dev/null differ diff --git a/Assets/notch-example--thumbnail.png b/Assets/notch-example--thumbnail.png deleted file mode 100644 index 3c574d6..0000000 Binary files a/Assets/notch-example--thumbnail.png and /dev/null differ diff --git a/Assets/notch-example.png b/Assets/notch-example.png deleted file mode 100644 index 4eff51f..0000000 Binary files a/Assets/notch-example.png and /dev/null differ diff --git a/Assets/notch-iphone-12-pro-thumbnail.png b/Assets/notch-iphone-12-pro-thumbnail.png new file mode 100644 index 0000000..c019551 Binary files /dev/null and b/Assets/notch-iphone-12-pro-thumbnail.png differ diff --git a/Assets/notch-iphone-12-pro.mov b/Assets/notch-iphone-12-pro.mov new file mode 100644 index 0000000..27d94db Binary files /dev/null and b/Assets/notch-iphone-12-pro.mov differ diff --git a/Assets/notch-iphone-12-pro.png b/Assets/notch-iphone-12-pro.png new file mode 100644 index 0000000..60b67fb Binary files /dev/null and b/Assets/notch-iphone-12-pro.png differ diff --git a/Assets/notch-iphone-13-pro-max-thumbnail.png b/Assets/notch-iphone-13-pro-max-thumbnail.png new file mode 100644 index 0000000..d6c0fda Binary files /dev/null and b/Assets/notch-iphone-13-pro-max-thumbnail.png differ diff --git a/Assets/notch-iphone-13-pro-max.mov b/Assets/notch-iphone-13-pro-max.mov new file mode 100644 index 0000000..ac51f8f Binary files /dev/null and b/Assets/notch-iphone-13-pro-max.mov differ diff --git a/Assets/notch-iphone-13-pro-max.png b/Assets/notch-iphone-13-pro-max.png new file mode 100644 index 0000000..fe78b55 Binary files /dev/null and b/Assets/notch-iphone-13-pro-max.png differ diff --git a/Assets/relative-to-safe-area-thumbnail.png b/Assets/relative-to-safe-area-thumbnail.png new file mode 100644 index 0000000..8cf37bc Binary files /dev/null and b/Assets/relative-to-safe-area-thumbnail.png differ diff --git a/Assets/relative-to-safe-area.mov b/Assets/relative-to-safe-area.mov new file mode 100644 index 0000000..05dc759 Binary files /dev/null and b/Assets/relative-to-safe-area.mov differ diff --git a/Assets/relative-to-safe-area.png b/Assets/relative-to-safe-area.png new file mode 100644 index 0000000..6575476 Binary files /dev/null and b/Assets/relative-to-safe-area.png differ diff --git a/Assets/safe-area-example--thumbnail.png b/Assets/safe-area-example--thumbnail.png deleted file mode 100644 index c26c7ef..0000000 Binary files a/Assets/safe-area-example--thumbnail.png and /dev/null differ diff --git a/Assets/safe-area-example.png b/Assets/safe-area-example.png deleted file mode 100644 index 568805e..0000000 Binary files a/Assets/safe-area-example.png and /dev/null differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 7240dc0..1eed460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). @@ -7,201 +8,298 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [4.0.0] - 2026-01-31 + +### Changed + +- Complete rewrite using modern Swift features including the [`Observation`](https://developer.apple.com/documentation/Observation) framework. + Therefore the minimum required iOS version increased to iOS `26.0` and Swift `6.2`. +- Reorganized project structure to follow Swift Package Manager conventions (`Sources/` and `Tests/` directories). +- Rewrote example application using SwiftUI with improved feature demonstrations. +- Updated README with comprehensive documentation and new screenshots. + +### Removed + +- Dropped support for CocoaPods. This follows CocoaPods' [read-only trunk notice](https://blog.cocoapods.org/CocoaPods-Specs-Repo/). Please migrate to Swift Package Manager. +- Dropped support for Carthage. Please migrate to Swift Package Manager. +- Removed legacy example project and Podfile infrastructure. + ## [3.0.0] - 2022-07-10 + ### Changed - - Use `Combine` instead of dependency `LightweightObservable`. Therefore the minimum required iOS version increased to 13. Otherwise there are no breaking changes. + +- Use `Combine` instead of dependency `LightweightObservable`. Therefore the minimum required iOS version increased to iOS `13.0`. Otherwise there are no breaking changes. ## [2.3.5] - 2022-12-09 + ### Fixed - - Fixed wrong minimum iOS deployment target when using Carthage. + +- Fixed wrong minimum iOS deployment target when using Carthage. ## [2.3.4] - 2022-11-09 + ### Fixed - - Added missing `UIKit` import when using Swift Package Manager. + +- Added missing `UIKit` import when using Swift Package Manager. ## [2.3.3] - 2022-08-09 + ### Changed - - Added specific BĂ©zier Path for `NotchGradientLoadingBar` for "iPhone 14" and "iPhone 14 Plus" + +- Added specific BĂ©zier Path for `NotchGradientLoadingBar` for "iPhone 14" and "iPhone 14 Plus" ## [2.3.2] - 2022-08-09 + ### Changed - - Improved BĂ©zier Path of `NotchGradientLoadingBar` - - Adapt logic of `UIKit` variant for calculating and animating the gradient to `SwiftUI` variant. This should not make any difference visually, but will be easier to maintain. + +- Improved BĂ©zier Path of `NotchGradientLoadingBar` +- Adapt logic of `UIKit` variant for calculating and animating the gradient to `SwiftUI` variant. This should not make any difference visually, but will be easier to maintain. + ### Fixed - - Fixed incorrect layout on `NotchGradientLoadingBar` when using "iPhone 12 Mini" or "iPhone 13 Mini" + +- Fixed incorrect layout on `NotchGradientLoadingBar` when using "iPhone 12 Mini" or "iPhone 13 Mini" ## [2.3.1] - 2022-10-05 + ### Fixed - - Fixed incorrect layout on `NotchGradientLoadingBar` when using "iPhone 11 Pro" or "iPhone 11 Pro Max" ([#029]) + +- Fixed incorrect layout on `NotchGradientLoadingBar` when using "iPhone 11 Pro" or "iPhone 11 Pro Max" ([#029]) [#029]: https://github.com/fxm90/GradientLoadingBar/issues/29 ## [2.3.0] - 2022-06-05 + ### Added - - Add `GradientLoadingBarView` to support SwiftUI + +- Add `GradientLoadingBarView` to support SwiftUI ## [2.2.5] - 2022-27-03 + ### Fixed - - Fixed incorrect layout on `NotchGradientLoadingBar` when using "iPhone 11" + +- Fixed incorrect layout on `NotchGradientLoadingBar` when using "iPhone 11" ## [2.2.4] - 2022-16-03 + ### Fixed - - Fixed incorrect layout on `NotchGradientLoadingBar` when using "iPhone XR" + +- Fixed incorrect layout on `NotchGradientLoadingBar` when using "iPhone XR" ## [2.2.3] - 2021-29-12 + ### Fixed - - Fixed incorrect layout on `NotchGradientLoadingBar` when using an increased height ([#026], thanks to [alinfarcas12](https://github.com/alinfarcas12)) + +- Fixed incorrect layout on `NotchGradientLoadingBar` when using an increased height ([#026], thanks to [alinfarcas12](https://github.com/alinfarcas12)) [#026]: https://github.com/fxm90/GradientLoadingBar/issues/26 ## [2.2.2] - 2021-05-10 + ### Fixed - - Fixed missing `return` ([#025]) + +- Fixed missing `return` ([#025]) [#025]: https://github.com/fxm90/GradientLoadingBar/issues/25 ## [2.2.1] - 2021-05-10 + ### Fixed - - Fixed invalid import ([#024]) + +- Fixed invalid import ([#024]) [#024]: https://github.com/fxm90/GradientLoadingBar/issues/24 ## [2.2.0] - 2021-04-10 + ### Fixed - - Added support for iPhone 13 ([#023]) + +- Added support for iPhone 13 ([#023]) [#023]: https://github.com/fxm90/GradientLoadingBar/issues/23 ## [2.1.1] - 2021-12-05 + ### Fixed - - Showing loading bar with a masked notch on iPad Pro 12.9 ([#020]) + +- Showing loading bar with a masked notch on iPad Pro 12.9 ([#020]) [#020]: https://github.com/fxm90/GradientLoadingBar/issues/20 ## [2.1.0] - 2020-17-06 + ### Added - - Added `NotchGradientLoadingBar`: A subclass of `GradientLoadingBar`, wrapping the `GradientActivityIndicatorView` around the notch of the iPhone. + +- Added `NotchGradientLoadingBar`: A subclass of `GradientLoadingBar`, wrapping the `GradientActivityIndicatorView` around the notch of the iPhone. ## [2.0.3] - 2020-18-01 + ### Added - - Added support for Swift Package Manager. + +- Added support for Swift Package Manager. ## [2.0.2] - 2019-20-12 + ### Changed - - Use `windows.first(where: { $0.isKeyWindow })` instead of [`keyWindow`](https://developer.apple.com/documentation/uikit/uiapplication/1622924-keywindow) as this is deprecated in iOS 13 + +- Use `windows.first(where: { $0.isKeyWindow })` instead of [`keyWindow`](https://developer.apple.com/documentation/uikit/uiapplication/1622924-keywindow) as this is deprecated in iOS 13 ## [2.0.1] - 2019-12-10 + ### Changed - - Refactorings (Optimized progress animation) - - Updated example application for iOS 13 + +- Refactorings (Optimized progress animation) +- Updated example application for iOS 13 ## [2.0.0] - 2019-02-09 + ### Changed - - Splitted framework into two classes: - - **GradientLoadingBar**: A controller, managing the visibility of the `GradientActivityIndicatorView` on the current key window. - - **GradientActivityIndicatorView**: A `UIView` containing the gradient with the animation, with support for Interface Builder. + +- Split framework into two classes: +- **GradientLoadingBar**: A controller, managing the visibility of the `GradientActivityIndicatorView` on the current key window. +- **GradientActivityIndicatorView**: A `UIView` containing the gradient with the animation, with support for Interface Builder. ## [1.1.17] - 2019-30-06 + ### Changed - - Extract observable implementation into a framework (`LightweightObservable`) and added it as dependency. + +- Extract observable implementation into a framework (`LightweightObservable`) and added it as dependency. ## [1.1.16] - 2019-08-04 + ### Changed -- Changed access controll level for extensions to `internal` to avoid conflicts + +- Changed access control level for extensions to `internal` to avoid conflicts - Added by [Emil Bellmann](https://github.com/emilrb) in Pull Request [#13](https://github.com/fxm90/GradientLoadingBar/pull/13). - Added support for Swift 5.0 ## [1.1.15] - 2019-14-02 + ### Changed + - Remove dependency `Observable` in favour of a more lightweight implementation - Small internal refactorings and cleanup ## [1.1.14] - 2018-20-12 + ### Changed + - Fixed fade-out animation not working in iOS 12 (https://openradar.appspot.com/46753872) ## [1.1.13] - 2018-14-11 + ### Changed + - Adapt code to support new version from dependency `Observable` ## [1.1.12] - 2018-22-09 + ### Changed + - Updates for Swift 4.2 - Refactored to observables - Removed `UIColor` initializers, as they're not required for the project to work (and it's not very common to use hex color codes in iOS) - - If you need them in your project, feel free to copy & paste to following file into you project: https://gist.github.com/fxm90/1350d27abf92af3be59aaa9eb72c9310 + - If you need them in your project, feel free to copy & paste the following file into your project: https://gist.github.com/fxm90/1350d27abf92af3be59aaa9eb72c9310 ## [1.1.11] - 2018-04-06 + ### Changed - - Carthage Support + +- Carthage Support ## [1.1.10] - 2018-14-04 + ### Changed - - Updates for Swift 4.1 - - Formatted code + +- Updates for Swift 4.1 +- Formatted code ## [1.1.9] - 2018-27-01 + ### Changed - - Formatted code - - Refactorings + +- Formatted code +- Refactorings ## [1.1.8] - 2018-20-01 + ### Added - - Further documentation - - Further examples + +- Further documentation +- Further examples + ### Changed - - Refactorings (moved all logic into view model) + +- Refactorings (moved all logic into view model) ## [1.1.7] - 2017-12-30 + ### Changed - - Adapt layout for iPhoneX - - Convert code to Swift 4 - - Refactor code to MVVM + +- Adapt layout for iPhoneX +- Convert code to Swift 4 +- Refactor code to MVVM ## [1.1.6] - 2017-10-30 + ### Added - - Allow setting a custom superview - - Documentation + +- Allow setting a custom superview +- Documentation ## [1.1.5] - 2017-10-03 + ### Fixed - - Struct 'DefaultValues' is private and cannot be referenced from a default argument value ([#001]) + +- Struct 'DefaultValues' is private and cannot be referenced from a default argument value ([#001]) [#001]: https://github.com/fxm90/GradientLoadingBar/issues/1 ## [1.1.4] - 2017-10-01 + ### Added - - Refactored project structure to match "pod lib create" / TravisCI integration - - Added example project + +- Refactored project structure to match "pod lib create" / TravisCI integration +- Added example project ## [1.1.3] - 2017-09-25 + ### Fixed - - Fixed never adding gradient view to "keyWindow" if it is not available on first call to "show()" + +- Fixed never adding gradient view to "keyWindow" if it is not available on first call to "show()" ## [1.1.2] - 2017-08-27 + ### Added - - Basic tests / TravisCI integration - - Refactored extension for UIColor initializer - - Changelog + +- Basic tests / TravisCI integration +- Refactored extension for UIColor initializer +- Changelog ## [1.1.1] - 2017-08-18 + ### Fixed - - Fixed optical animation issue - - Fixed orientation change bug + +- Fixed optical animation issue +- Fixed orientation change bug + ### Changed - - Refactor entire code - - Lint code + +- Refactor entire code +- Lint code ## [1.1.0] - 2017-02-28 + ### Changed - - Allow changing gradient colors + +- Allow changing gradient colors ## [1.0.0] - 2016-12-28 -- Initial release +- Initial release -[Unreleased]: https://github.com/fxm90/GradientLoadingBar/compare/3.0.0...main -[2.3.5]: https://github.com/fxm90/GradientLoadingBar/compare/2.3.5...3.0.0 +[Unreleased]: https://github.com/fxm90/GradientLoadingBar/compare/4.0.0...main +[4.0.0]: https://github.com/fxm90/GradientLoadingBar/compare/3.0.0...4.0.0 +[3.0.0]: https://github.com/fxm90/GradientLoadingBar/compare/2.3.5...3.0.0 [2.3.5]: https://github.com/fxm90/GradientLoadingBar/compare/2.3.4...2.3.5 [2.3.4]: https://github.com/fxm90/GradientLoadingBar/compare/2.3.3...2.3.4 [2.3.3]: https://github.com/fxm90/GradientLoadingBar/compare/2.3.2...2.3.3 diff --git a/Cartfile b/Cartfile deleted file mode 100644 index e69de29..0000000 diff --git a/Example/.swiftformat b/Example/.swiftformat deleted file mode 100644 index a339cb8..0000000 --- a/Example/.swiftformat +++ /dev/null @@ -1,19 +0,0 @@ -# -# .swiftformat -# Created by Felix Mau (https://felix.hamburg) -# - -# -# "Global" formatting options -# -# "Global" formatting options will be applied from the `SwiftConfigurationFiles` repository. -# See https://github.com/fxm90/SwiftConfigurationFiles for further details. -# - -# -# File options -# -# Note: Excluded paths are relative to the SwiftFormat configuration file. -# Therefore we can't specify them in the configuration file from the `SwiftConfigurationFiles` repository. -# ---exclude Pods \ No newline at end of file diff --git a/Example/.swiftlint.yml b/Example/.swiftlint.yml deleted file mode 100644 index 6275d0e..0000000 --- a/Example/.swiftlint.yml +++ /dev/null @@ -1,26 +0,0 @@ -ï»ż# -# .swiftlint.yml -# Created by Felix Mau (https://felix.hamburg) -# - -opt_in_rules: - - file_header - -file_header: - required_pattern: | - \/\/ - \/\/ SWIFTLINT_CURRENT_FILENAME - \/\/ (GradientLoadingBar|Example|ExampleTests|ExampleSnapshotTests) - \/\/ - \/\/ Created by .*? on \d{2}\.\d{2}\.\d{2}\. - \/\/ Copyright © \d{4} .*?\. All rights reserved\. - \/\/ - -included: - - ./ - - ../GradientLoadingBar - -excluded: - - Pods - -parent_config: Pods/SwiftConfigurationFiles/.swiftlint.yml \ No newline at end of file diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj deleted file mode 100644 index 99e73fc..0000000 --- a/Example/Example.xcodeproj/project.pbxproj +++ /dev/null @@ -1,586 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 55; - objects = { - -/* Begin PBXBuildFile section */ - DC4E50A16A3B84900FBD1981 /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12DDBD589B02F5D3C9C3AF69 /* Pods_Example.framework */; }; - DCA9833A27D68AD400D6EA30 /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA9833927D68AD400D6EA30 /* ExampleApp.swift */; }; - DCA9833E27D68AD600D6EA30 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DCA9833D27D68AD600D6EA30 /* Assets.xcassets */; }; - DCA9834127D68AD600D6EA30 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DCA9834027D68AD600D6EA30 /* Preview Assets.xcassets */; }; - DCA9836927D68E3500D6EA30 /* StoryboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA9836827D68E3500D6EA30 /* StoryboardView.swift */; }; - DCA9836C27D6905000D6EA30 /* EntryPointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA9836B27D6905000D6EA30 /* EntryPointView.swift */; }; - DCA9837127D6912300D6EA30 /* BasicExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA9837027D6912300D6EA30 /* BasicExampleViewController.swift */; }; - DCA9837627D693BB00D6EA30 /* BasicExample.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DCA9837527D693BB00D6EA30 /* BasicExample.storyboard */; }; - DCA9837827D6951C00D6EA30 /* SafeAreaExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA9837727D6951C00D6EA30 /* SafeAreaExampleViewController.swift */; }; - DCA9837A27D6958500D6EA30 /* SafeAreaExample.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DCA9837927D6958500D6EA30 /* SafeAreaExample.storyboard */; }; - DCA9838727D6A44700D6EA30 /* NavigationBarExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA9838627D6A44700D6EA30 /* NavigationBarExampleViewController.swift */; }; - DCA9838927D6A55A00D6EA30 /* NavigationBarExample.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DCA9838827D6A55A00D6EA30 /* NavigationBarExample.storyboard */; }; - DCA9838B27D6A74800D6EA30 /* SwiftUIExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA9838A27D6A74800D6EA30 /* SwiftUIExampleView.swift */; }; - F50E4C7E28061655001232D3 /* AdvancedExample.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F50E4C7D28061655001232D3 /* AdvancedExample.storyboard */; }; - F50E4C8028061715001232D3 /* AdvancedExampleTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F50E4C7F28061715001232D3 /* AdvancedExampleTableViewController.swift */; }; - F5C7E8882809CDEE00036B8E /* CustomColorsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C7E8872809CDEE00036B8E /* CustomColorsTableViewCell.swift */; }; - F5C7E88A2809CDF600036B8E /* InterfaceBuilderSetupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C7E8892809CDF600036B8E /* InterfaceBuilderSetupTableViewCell.swift */; }; - F5C7E88C2809CDFD00036B8E /* CustomSuperviewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C7E88B2809CDFD00036B8E /* CustomSuperviewTableViewCell.swift */; }; - F5C7E88F2809CF1200036B8E /* BorderedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5C7E88E2809CF1200036B8E /* BorderedButton.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 12DDBD589B02F5D3C9C3AF69 /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3299012A62A2E4310D02CD78 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; - DCA9833627D68AD400D6EA30 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; - DCA9833927D68AD400D6EA30 /* ExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleApp.swift; sourceTree = ""; }; - DCA9833D27D68AD600D6EA30 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - DCA9834027D68AD600D6EA30 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - DCA9836827D68E3500D6EA30 /* StoryboardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardView.swift; sourceTree = ""; }; - DCA9836B27D6905000D6EA30 /* EntryPointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryPointView.swift; sourceTree = ""; }; - DCA9837027D6912300D6EA30 /* BasicExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicExampleViewController.swift; sourceTree = ""; }; - DCA9837527D693BB00D6EA30 /* BasicExample.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = BasicExample.storyboard; sourceTree = ""; }; - DCA9837727D6951C00D6EA30 /* SafeAreaExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeAreaExampleViewController.swift; sourceTree = ""; }; - DCA9837927D6958500D6EA30 /* SafeAreaExample.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SafeAreaExample.storyboard; sourceTree = ""; }; - DCA9838627D6A44700D6EA30 /* NavigationBarExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarExampleViewController.swift; sourceTree = ""; }; - DCA9838827D6A55A00D6EA30 /* NavigationBarExample.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NavigationBarExample.storyboard; sourceTree = ""; }; - DCA9838A27D6A74800D6EA30 /* SwiftUIExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIExampleView.swift; sourceTree = ""; }; - F50E4C7D28061655001232D3 /* AdvancedExample.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AdvancedExample.storyboard; sourceTree = ""; }; - F50E4C7F28061715001232D3 /* AdvancedExampleTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedExampleTableViewController.swift; sourceTree = ""; }; - F5C7E8872809CDEE00036B8E /* CustomColorsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomColorsTableViewCell.swift; sourceTree = ""; }; - F5C7E8892809CDF600036B8E /* InterfaceBuilderSetupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceBuilderSetupTableViewCell.swift; sourceTree = ""; }; - F5C7E88B2809CDFD00036B8E /* CustomSuperviewTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSuperviewTableViewCell.swift; sourceTree = ""; }; - F5C7E88E2809CF1200036B8E /* BorderedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BorderedButton.swift; sourceTree = ""; }; - FAA1A12E84EBC1F48401EB10 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - DCA9833327D68AD400D6EA30 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - DC4E50A16A3B84900FBD1981 /* Pods_Example.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 33D2CE217238EAE7A5515634 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 12DDBD589B02F5D3C9C3AF69 /* Pods_Example.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - A6AC3866473F1A669D221CA4 /* Pods */ = { - isa = PBXGroup; - children = ( - FAA1A12E84EBC1F48401EB10 /* Pods-Example.debug.xcconfig */, - 3299012A62A2E4310D02CD78 /* Pods-Example.release.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; - DCA9832D27D68AD400D6EA30 = { - isa = PBXGroup; - children = ( - DCA9833827D68AD400D6EA30 /* Example */, - DCA9833727D68AD400D6EA30 /* Products */, - A6AC3866473F1A669D221CA4 /* Pods */, - 33D2CE217238EAE7A5515634 /* Frameworks */, - ); - sourceTree = ""; - }; - DCA9833727D68AD400D6EA30 /* Products */ = { - isa = PBXGroup; - children = ( - DCA9833627D68AD400D6EA30 /* Example.app */, - ); - name = Products; - sourceTree = ""; - }; - DCA9833827D68AD400D6EA30 /* Example */ = { - isa = PBXGroup; - children = ( - DCA9836727D68E3500D6EA30 /* Helper */, - DCA9836A27D68E3C00D6EA30 /* Scenes */, - DCA9833927D68AD400D6EA30 /* ExampleApp.swift */, - DCA9833D27D68AD600D6EA30 /* Assets.xcassets */, - DCA9833F27D68AD600D6EA30 /* Preview Content */, - ); - path = Example; - sourceTree = ""; - }; - DCA9833F27D68AD600D6EA30 /* Preview Content */ = { - isa = PBXGroup; - children = ( - DCA9834027D68AD600D6EA30 /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; - DCA9836727D68E3500D6EA30 /* Helper */ = { - isa = PBXGroup; - children = ( - DCA9836827D68E3500D6EA30 /* StoryboardView.swift */, - ); - path = Helper; - sourceTree = ""; - }; - DCA9836A27D68E3C00D6EA30 /* Scenes */ = { - isa = PBXGroup; - children = ( - DCA9836E27D6909A00D6EA30 /* AdvancedExample */, - DCA9836D27D6908F00D6EA30 /* BasicExample */, - DCA9837427D691C600D6EA30 /* NavigationBarExample */, - DCA9837227D691A200D6EA30 /* SafeAreaExample */, - DCA9836F27D690A400D6EA30 /* SwiftUIExample */, - DCA9836B27D6905000D6EA30 /* EntryPointView.swift */, - ); - path = Scenes; - sourceTree = ""; - }; - DCA9836D27D6908F00D6EA30 /* BasicExample */ = { - isa = PBXGroup; - children = ( - DCA9837527D693BB00D6EA30 /* BasicExample.storyboard */, - DCA9837027D6912300D6EA30 /* BasicExampleViewController.swift */, - ); - path = BasicExample; - sourceTree = ""; - }; - DCA9836E27D6909A00D6EA30 /* AdvancedExample */ = { - isa = PBXGroup; - children = ( - F5C7E8862809CDE200036B8E /* Cells */, - F5C7E88D2809CF0700036B8E /* Views */, - F50E4C7D28061655001232D3 /* AdvancedExample.storyboard */, - F50E4C7F28061715001232D3 /* AdvancedExampleTableViewController.swift */, - ); - path = AdvancedExample; - sourceTree = ""; - }; - DCA9836F27D690A400D6EA30 /* SwiftUIExample */ = { - isa = PBXGroup; - children = ( - DCA9838A27D6A74800D6EA30 /* SwiftUIExampleView.swift */, - ); - path = SwiftUIExample; - sourceTree = ""; - }; - DCA9837227D691A200D6EA30 /* SafeAreaExample */ = { - isa = PBXGroup; - children = ( - DCA9837927D6958500D6EA30 /* SafeAreaExample.storyboard */, - DCA9837727D6951C00D6EA30 /* SafeAreaExampleViewController.swift */, - ); - path = SafeAreaExample; - sourceTree = ""; - }; - DCA9837427D691C600D6EA30 /* NavigationBarExample */ = { - isa = PBXGroup; - children = ( - DCA9838827D6A55A00D6EA30 /* NavigationBarExample.storyboard */, - DCA9838627D6A44700D6EA30 /* NavigationBarExampleViewController.swift */, - ); - path = NavigationBarExample; - sourceTree = ""; - }; - F5C7E8862809CDE200036B8E /* Cells */ = { - isa = PBXGroup; - children = ( - F5C7E8872809CDEE00036B8E /* CustomColorsTableViewCell.swift */, - F5C7E8892809CDF600036B8E /* InterfaceBuilderSetupTableViewCell.swift */, - F5C7E88B2809CDFD00036B8E /* CustomSuperviewTableViewCell.swift */, - ); - path = Cells; - sourceTree = ""; - }; - F5C7E88D2809CF0700036B8E /* Views */ = { - isa = PBXGroup; - children = ( - F5C7E88E2809CF1200036B8E /* BorderedButton.swift */, - ); - path = Views; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - DCA9833527D68AD400D6EA30 /* Example */ = { - isa = PBXNativeTarget; - buildConfigurationList = DCA9835A27D68AD600D6EA30 /* Build configuration list for PBXNativeTarget "Example" */; - buildPhases = ( - 52082F0E9B4B579867A82146 /* [CP] Check Pods Manifest.lock */, - DCA9833227D68AD400D6EA30 /* Sources */, - DCA9833327D68AD400D6EA30 /* Frameworks */, - DCA9833427D68AD400D6EA30 /* Resources */, - C56CF067D28EB40785524B49 /* [CP] Embed Pods Frameworks */, - DCA9836527D68D6400D6EA30 /* SwiftFormat */, - DCA9836627D68D7000D6EA30 /* SwiftLint */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Example; - productName = Example; - productReference = DCA9833627D68AD400D6EA30 /* Example.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - DCA9832E27D68AD400D6EA30 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1320; - LastUpgradeCheck = 1320; - TargetAttributes = { - DCA9833527D68AD400D6EA30 = { - CreatedOnToolsVersion = 13.2.1; - }; - }; - }; - buildConfigurationList = DCA9833127D68AD400D6EA30 /* Build configuration list for PBXProject "Example" */; - compatibilityVersion = "Xcode 13.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = DCA9832D27D68AD400D6EA30; - productRefGroup = DCA9833727D68AD400D6EA30 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - DCA9833527D68AD400D6EA30 /* Example */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - DCA9833427D68AD400D6EA30 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DCA9834127D68AD600D6EA30 /* Preview Assets.xcassets in Resources */, - F50E4C7E28061655001232D3 /* AdvancedExample.storyboard in Resources */, - DCA9837A27D6958500D6EA30 /* SafeAreaExample.storyboard in Resources */, - DCA9837627D693BB00D6EA30 /* BasicExample.storyboard in Resources */, - DCA9833E27D68AD600D6EA30 /* Assets.xcassets in Resources */, - DCA9838927D6A55A00D6EA30 /* NavigationBarExample.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 52082F0E9B4B579867A82146 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Example-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - C56CF067D28EB40785524B49 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - DCA9836527D68D6400D6EA30 /* SwiftFormat */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = SwiftFormat; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "SWIFTFORMAT=\"${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat\"\nif [ ! -f \"$SWIFTFORMAT\" ]; then\n echo \"warning: SwiftFormat not installed!\"\n exit 1\nfi\n\n$SWIFTFORMAT ../ --config \"${PODS_ROOT}/SwiftConfigurationFiles/.swiftformat\"\n"; - }; - DCA9836627D68D7000D6EA30 /* SwiftLint */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = SwiftLint; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "SWIFTLINT=\"${PODS_ROOT}/SwiftLint/swiftlint\"\nif [ ! -f \"$SWIFTLINT\" ]; then\n echo \"warning: SwiftLint not installed!\"\n exit 1\nfi\n\n$SWIFTLINT\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - DCA9833227D68AD400D6EA30 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DCA9837127D6912300D6EA30 /* BasicExampleViewController.swift in Sources */, - DCA9833A27D68AD400D6EA30 /* ExampleApp.swift in Sources */, - F5C7E88F2809CF1200036B8E /* BorderedButton.swift in Sources */, - F5C7E8882809CDEE00036B8E /* CustomColorsTableViewCell.swift in Sources */, - DCA9836927D68E3500D6EA30 /* StoryboardView.swift in Sources */, - F5C7E88C2809CDFD00036B8E /* CustomSuperviewTableViewCell.swift in Sources */, - F5C7E88A2809CDF600036B8E /* InterfaceBuilderSetupTableViewCell.swift in Sources */, - F50E4C8028061715001232D3 /* AdvancedExampleTableViewController.swift in Sources */, - DCA9836C27D6905000D6EA30 /* EntryPointView.swift in Sources */, - DCA9837827D6951C00D6EA30 /* SafeAreaExampleViewController.swift in Sources */, - DCA9838727D6A44700D6EA30 /* NavigationBarExampleViewController.swift in Sources */, - DCA9838B27D6A74800D6EA30 /* SwiftUIExampleView.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - DCA9835827D68AD600D6EA30 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - DCA9835927D68AD600D6EA30 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - DCA9835B27D68AD600D6EA30 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = FAA1A12E84EBC1F48401EB10 /* Pods-Example.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; - DEVELOPMENT_TEAM = F63GJC2AN7; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = hamburg.felix.gradientLoadingBar.example.Example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - DCA9835C27D68AD600D6EA30 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 3299012A62A2E4310D02CD78 /* Pods-Example.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; - DEVELOPMENT_TEAM = F63GJC2AN7; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = hamburg.felix.gradientLoadingBar.example.Example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - DCA9833127D68AD400D6EA30 /* Build configuration list for PBXProject "Example" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DCA9835827D68AD600D6EA30 /* Debug */, - DCA9835927D68AD600D6EA30 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - DCA9835A27D68AD600D6EA30 /* Build configuration list for PBXNativeTarget "Example" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - DCA9835B27D68AD600D6EA30 /* Debug */, - DCA9835C27D68AD600D6EA30 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = DCA9832E27D68AD400D6EA30 /* Project object */; -} diff --git a/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/Example/Example.xcworkspace/contents.xcworkspacedata b/Example/Example.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index a37cf19..0000000 --- a/Example/Example.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 9221b9b..0000000 --- a/Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Example/Example/ExampleApp.swift b/Example/Example/ExampleApp.swift deleted file mode 100644 index 82786fc..0000000 --- a/Example/Example/ExampleApp.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// ExampleApp.swift -// Example -// -// Created by Felix Mau on 07.03.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import SwiftUI - -@main -struct ExampleApp: App { - var body: some Scene { - WindowGroup { - EntryPointView() - .onAppear { - // Use iOS 13 navigation bar appearance. - // - // While the entry point scene looks better with an opaque navigation background, the child scenes look - // better with the default navigation background. As we can't update the appearance per scene we fallback - // to an appearance with the default background here. - // - // Based on: https://sarunw.com/posts/uinavigationbar-changes-in-ios13/ - let appearance = UINavigationBarAppearance() - appearance.configureWithDefaultBackground() - - UINavigationBar.appearance().scrollEdgeAppearance = appearance - } - } - } -} diff --git a/Example/Example/Helper/StoryboardView.swift b/Example/Example/Helper/StoryboardView.swift deleted file mode 100644 index 64a8d2c..0000000 --- a/Example/Example/Helper/StoryboardView.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// StoryboardView.swift -// Example -// -// Created by Felix Mau on 02.02.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import SwiftUI - -struct StoryboardView: UIViewControllerRepresentable { - - // MARK: - Public properties - - let name: String - - // MARK: - Public methods - - func makeUIViewController(context _: Context) -> UIViewController { - let storyboard = UIStoryboard(name: name, bundle: nil) - guard let viewController = storyboard.instantiateInitialViewController() else { - fatalError("⚠ – Could not instantiate initial view controller for storyboard with name `\(name)`.") - } - - return viewController - } - - func updateUIViewController(_: UIViewController, context _: Context) {} -} diff --git a/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json b/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/Example/Example/Preview Content/Preview Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Example/Example/Scenes/AdvancedExample/AdvancedExample.storyboard b/Example/Example/Scenes/AdvancedExample/AdvancedExample.storyboard deleted file mode 100644 index 9ae3a94..0000000 --- a/Example/Example/Scenes/AdvancedExample/AdvancedExample.storyboard +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/Example/Scenes/AdvancedExample/AdvancedExampleTableViewController.swift b/Example/Example/Scenes/AdvancedExample/AdvancedExampleTableViewController.swift deleted file mode 100644 index 7d294f3..0000000 --- a/Example/Example/Scenes/AdvancedExample/AdvancedExampleTableViewController.swift +++ /dev/null @@ -1,136 +0,0 @@ -// -// AdvancedExampleTableViewController.swift -// Example -// -// Created by Felix Mau on 12.04.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import GradientLoadingBar -import SwiftUI -import UIKit - -final class AdvancedExampleTableViewController: UITableViewController { - - // MARK: - Config - - private enum Config { - /// The custom gradient colors we use. - /// Source: https://color.adobe.com/Pink-Flamingo-color-theme-10343714/ - static let gradientColors = [ - #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), - ] - - static let firstSectionHeaderHeight: CGFloat = 48 - static let defaultSectionHeaderHeight: CGFloat = 32 - } - - // MARK: - Types - - private enum Section: CaseIterable { - case customColors - case interfaceBuilderSetup - case customSuperview - - var cellIdentifier: String { - switch self { - case .customColors: - return CustomColorsTableViewCell.reuseIdentifier - - case .interfaceBuilderSetup: - return InterfaceBuilderSetupTableViewCell.reuseIdentifier - - case .customSuperview: - return CustomSuperviewTableViewCell.reuseIdentifier - } - } - - var sectionTitle: String { - switch self { - case .customColors: - return "Custom Colors" - - case .interfaceBuilderSetup: - return "Interface Builder Setup" - - case .customSuperview: - return "Custom Superview" - } - } - - var sectionHeaderHeight: CGFloat { - switch self { - case .customColors: - return Config.firstSectionHeaderHeight - - case .interfaceBuilderSetup, .customSuperview: - return Config.defaultSectionHeaderHeight - } - } - } - - // MARK: - Private properties - - private let gradientLoadingBar = GradientLoadingBar() - - // MARK: - Public methods - - override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = .systemGroupedBackground - - gradientLoadingBar.gradientColors = Config.gradientColors - } - - // MARK: - Table view data source - - override func numberOfSections(in _: UITableView) -> Int { - Section.allCases.count - } - - override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { - 1 - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let section = Section.allCases[indexPath.section] - switch section { - case .customColors: - guard let cell = tableView.dequeueReusableCell(withIdentifier: section.cellIdentifier, for: indexPath) as? CustomColorsTableViewCell else { - fatalError("⚠ – Invalid table view setup. Expected to have `CustomColorsTableViewCell` at this point!") - } - - cell.tapHandler = { [weak self] in - if self?.gradientLoadingBar.isHidden == true { - self?.gradientLoadingBar.fadeIn() - } else { - self?.gradientLoadingBar.fadeOut() - } - } - - return cell - - case .interfaceBuilderSetup, .customSuperview: - return tableView.dequeueReusableCell(withIdentifier: section.cellIdentifier, for: indexPath) - } - } - - override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? { - Section.allCases[section].sectionTitle - } - - override func tableView(_: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - Section.allCases[section].sectionHeaderHeight - } -} - -// MARK: - Helper - -struct AdvancedExampleView: View { - var body: some View { - StoryboardView(name: "AdvancedExample") - .navigationTitle("🚀 Advanced Example") - .edgesIgnoringSafeArea(.bottom) - } -} diff --git a/Example/Example/Scenes/AdvancedExample/Cells/CustomColorsTableViewCell.swift b/Example/Example/Scenes/AdvancedExample/Cells/CustomColorsTableViewCell.swift deleted file mode 100644 index 61a5343..0000000 --- a/Example/Example/Scenes/AdvancedExample/Cells/CustomColorsTableViewCell.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// CustomColorsTableViewCell.swift -// Example -// -// Created by Felix Mau on 15.04.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import UIKit - -final class CustomColorsTableViewCell: UITableViewCell { - - // MARK: - Public properties - - static let reuseIdentifier = "CustomColorsTableViewCell" - - var tapHandler: (() -> Void)? - - // MARK: - Private methods - - @IBAction private func customColorsButtonTouchUpInside(_: Any) { - tapHandler?() - } -} diff --git a/Example/Example/Scenes/AdvancedExample/Cells/CustomSuperviewTableViewCell.swift b/Example/Example/Scenes/AdvancedExample/Cells/CustomSuperviewTableViewCell.swift deleted file mode 100644 index 34fb838..0000000 --- a/Example/Example/Scenes/AdvancedExample/Cells/CustomSuperviewTableViewCell.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// CustomSuperviewTableViewCell.swift -// Example -// -// Created by Felix Mau on 15.04.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import GradientLoadingBar -import UIKit - -final class CustomSuperviewTableViewCell: UITableViewCell { - - // MARK: - Config - - private enum Config { - static let height: CGFloat = 3 - } - - // MARK: - Outlets - - @IBOutlet private var toggleButton: UIButton! - - // MARK: - Public properties - - static let reuseIdentifier = "CustomSuperviewViewCell" - - // MARK: - Private properties - - private let gradientActivityIndicatorView = GradientActivityIndicatorView() - - // MARK: - Public methods - - override func awakeFromNib() { - super.awakeFromNib() - - setupGradientActivityIndicatorView() - } - - // MARK: - Private methods - - private func setupGradientActivityIndicatorView() { - gradientActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false - toggleButton.addSubview(gradientActivityIndicatorView) - - NSLayoutConstraint.activate([ - gradientActivityIndicatorView.leadingAnchor.constraint(equalTo: toggleButton.leadingAnchor), - gradientActivityIndicatorView.trailingAnchor.constraint(equalTo: toggleButton.trailingAnchor), - - gradientActivityIndicatorView.bottomAnchor.constraint(equalTo: toggleButton.bottomAnchor), - gradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: Config.height), - ]) - } - - @IBAction private func toggleButtonTouchUpInside(_: Any) { - if gradientActivityIndicatorView.isHidden { - gradientActivityIndicatorView.fadeIn() - } else { - gradientActivityIndicatorView.fadeOut() - } - } -} diff --git a/Example/Example/Scenes/AdvancedExample/Cells/InterfaceBuilderSetupTableViewCell.swift b/Example/Example/Scenes/AdvancedExample/Cells/InterfaceBuilderSetupTableViewCell.swift deleted file mode 100644 index 88079fa..0000000 --- a/Example/Example/Scenes/AdvancedExample/Cells/InterfaceBuilderSetupTableViewCell.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// InterfaceBuilderSetupTableViewCell.swift -// Example -// -// Created by Felix Mau on 15.04.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import GradientLoadingBar -import UIKit - -final class InterfaceBuilderSetupTableViewCell: UITableViewCell { - - // MARK: - Config - - private enum Config { - static let animationDuration: TimeInterval = 0.5 - } - - // MARK: - Outlets - - @IBOutlet private var gradientActivityIndicator: GradientActivityIndicatorView! - - // MARK: - Public properties - - static let reuseIdentifier = "InterfaceBuilderSetupTableViewCell" - - // MARK: - Private methods - - @IBAction private func toggleIBSetupButtonTouchUpInside(_: Any) { - // We explicitly "only" reduce the alpha here, as calling `fadeIn()` / `fadeOut()` would update the `isHidden` - // flag accordingly. This would then lead to a height-update of the parent stack view. - UIView.animate(withDuration: Config.animationDuration) { - if self.gradientActivityIndicator.alpha > 0 { - self.gradientActivityIndicator.alpha = 0 - } else { - self.gradientActivityIndicator.alpha = 1 - } - } - } -} diff --git a/Example/Example/Scenes/AdvancedExample/Views/BorderedButton.swift b/Example/Example/Scenes/AdvancedExample/Views/BorderedButton.swift deleted file mode 100644 index fba8592..0000000 --- a/Example/Example/Scenes/AdvancedExample/Views/BorderedButton.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// BorderedButton.swift -// Example -// -// Created by Felix Mau on 15.04.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import UIKit - -@IBDesignable -final class BorderedButton: UIButton { - - // MARK: - Config - - private enum Config { - static let borderColor = #colorLiteral(red: 0.2862745098, green: 0.5647058824, blue: 0.8862745098, alpha: 1) - static let borderWidth: CGFloat = 1 - static let cornerRadius: CGFloat = 4 - } - - // MARK: - Instance Lifecycle - - override init(frame: CGRect) { - super.init(frame: frame) - - commonInit() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - - commonInit() - } - - // MARK: - Private methods - - private func commonInit() { - backgroundColor = .clear - tintColor = Config.borderColor - - layer.borderColor = Config.borderColor.cgColor - layer.borderWidth = Config.borderWidth - layer.cornerRadius = Config.cornerRadius - layer.masksToBounds = true - } -} diff --git a/Example/Example/Scenes/BasicExample/BasicExample.storyboard b/Example/Example/Scenes/BasicExample/BasicExample.storyboard deleted file mode 100644 index 857b4b5..0000000 --- a/Example/Example/Scenes/BasicExample/BasicExample.storyboard +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/Example/Scenes/BasicExample/BasicExampleViewController.swift b/Example/Example/Scenes/BasicExample/BasicExampleViewController.swift deleted file mode 100644 index 949c900..0000000 --- a/Example/Example/Scenes/BasicExample/BasicExampleViewController.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// BasicExampleViewController.swift -// Example -// -// Created by Felix Mau on 07.03.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import GradientLoadingBar -import SwiftUI -import UIKit - -final class BasicExampleViewController: UIViewController { - - // MARK: - Public methods - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - // Reset any possible visible loading bar. - GradientLoadingBar.shared.fadeOut() - } - - // MARK: - Private methods - - @IBAction private func showButtonTouchUpInside(_: Any) { - GradientLoadingBar.shared.fadeIn() - } - - @IBAction private func hideButtonTouchUpInside(_: Any) { - GradientLoadingBar.shared.fadeOut() - } -} - -// MARK: - Helper - -struct BasicExampleView: View { - var body: some View { - StoryboardView(name: "BasicExample") - .navigationTitle("🏡 Basic Example") - } -} diff --git a/Example/Example/Scenes/EntryPointView.swift b/Example/Example/Scenes/EntryPointView.swift deleted file mode 100644 index 604f169..0000000 --- a/Example/Example/Scenes/EntryPointView.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// EntryPointView.swift -// Example -// -// Created by Felix Mau on 07.03.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import SwiftUI - -struct EntryPointView: View { - - // MARK: - Private properties - - @State - private var isNavigationBarExampleVisible = false - - // MARK: - Render - - var body: some View { - NavigationView { - List { - Section(header: Text("UIKit Examples")) { - NavigationLink(destination: BasicExampleView()) { - TitleSubtitleView(title: "🏡 Basic Example", - subtitle: "Basic usage and setup.") - } - - NavigationLink(destination: SafeAreaExampleView()) { - TitleSubtitleView(title: "đŸ“Č Safe Area Example", - subtitle: "Loading bar ignoring the safe area.") - } - - NavigationLink(destination: AdvancedExampleView()) { - TitleSubtitleView(title: "🚀 Advanced Example", - subtitle: "How to apply e.g. custom colors.") - } - - Button { - isNavigationBarExampleVisible = true - } label: { - TitleSubtitleView(title: "🧭 Navigation Bar Example", - subtitle: "Attach the loading bar to a navigation bar.") - // Make entire row tappable and not just the text. - .frame(maxWidth: .infinity, alignment: .leading) - .contentShape(Rectangle()) - } - .buttonStyle(PlainButtonStyle()) - } - - Section(header: Text("SwiftUI Examples")) { - NavigationLink(destination: SwiftUIExampleView()) { - TitleSubtitleView(title: "🎹 GradientLoadingBarView Example", - subtitle: "How to use the SwiftUI view.") - } - } - } - // We present the `NavigationBarExampleView` as a sheet using it's own navigation controller. - // The `NavigationView` passed from SwiftUI isn't accessible from a `UIViewController` in a way, that we can add subviews to it. - .sheet(isPresented: $isNavigationBarExampleVisible) { - NavigationBarExampleView() - } - // Unfortunately setting the title here results in constraint warnings. - // I couldn't find a possible fix yet, even `.navigationViewStyle(.stack)` doesn't seem to work. - // https://stackoverflow.com/q/65316497 - .navigationTitle("GradientLoadingBar") - .navigationBarTitleDisplayMode(.large) - } - } -} - -// MARK: - Subviews - -private struct TitleSubtitleView: View { - let title: String - let subtitle: String - - var body: some View { - VStack(alignment: .leading, spacing: 8) { - Text(title) - .font(.headline) - Text(subtitle) - .font(.subheadline) - } - .padding(.vertical, 8) - } -} - -// MARK: - Preview - -struct EntryPointView_Previews: PreviewProvider { - static var previews: some View { - EntryPointView() - } -} diff --git a/Example/Example/Scenes/NavigationBarExample/NavigationBarExample.storyboard b/Example/Example/Scenes/NavigationBarExample/NavigationBarExample.storyboard deleted file mode 100644 index 1b41d19..0000000 --- a/Example/Example/Scenes/NavigationBarExample/NavigationBarExample.storyboard +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/Example/Scenes/NavigationBarExample/NavigationBarExampleViewController.swift b/Example/Example/Scenes/NavigationBarExample/NavigationBarExampleViewController.swift deleted file mode 100644 index 3677ed1..0000000 --- a/Example/Example/Scenes/NavigationBarExample/NavigationBarExampleViewController.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// NavigationBarExampleViewController.swift -// Example -// -// Created by Felix Mau on 07.03.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import GradientLoadingBar -import SwiftUI -import UIKit - -final class NavigationBarExampleViewController: UIViewController { - - // MARK: - Config - - private enum Config { - /// The programatically applied height of the `GradientActivityIndicatorView`. - static let height: CGFloat = 3 - } - - // MARK: - Private properties - - private let gradientProgressIndicatorView = GradientActivityIndicatorView() - - // MARK: - Public methods - - override func viewDidLoad() { - super.viewDidLoad() - - let barButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonTapped)) - navigationItem.rightBarButtonItem = barButtonItem - - setupGradientProgressIndicatorView() - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - // Reset any possible visible loading bar. - gradientProgressIndicatorView.fadeOut() - } - - // MARK: - Private methods - - private func setupGradientProgressIndicatorView() { - guard let navigationBar = navigationController?.navigationBar else { return } - - gradientProgressIndicatorView.translatesAutoresizingMaskIntoConstraints = false - navigationBar.addSubview(gradientProgressIndicatorView) - - NSLayoutConstraint.activate([ - gradientProgressIndicatorView.leadingAnchor.constraint(equalTo: navigationBar.leadingAnchor), - gradientProgressIndicatorView.trailingAnchor.constraint(equalTo: navigationBar.trailingAnchor), - - gradientProgressIndicatorView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor), - gradientProgressIndicatorView.heightAnchor.constraint(equalToConstant: Config.height), - ]) - } - - @IBAction private func doneButtonTapped(_: Any) { - dismiss(animated: true) - } - - @IBAction private func showButtonTouchUpInside(_: Any) { - gradientProgressIndicatorView.fadeIn() - } - - @IBAction private func hideButtonTouchUpInside(_: Any) { - gradientProgressIndicatorView.fadeOut() - } -} - -// MARK: - Helper - -struct NavigationBarExampleView: View { - var body: some View { - StoryboardView(name: "NavigationBarExample") - } -} diff --git a/Example/Example/Scenes/SafeAreaExample/SafeAreaExample.storyboard b/Example/Example/Scenes/SafeAreaExample/SafeAreaExample.storyboard deleted file mode 100644 index 82f76d0..0000000 --- a/Example/Example/Scenes/SafeAreaExample/SafeAreaExample.storyboard +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/Example/Scenes/SafeAreaExample/SafeAreaExampleViewController.swift b/Example/Example/Scenes/SafeAreaExample/SafeAreaExampleViewController.swift deleted file mode 100644 index ee2ed90..0000000 --- a/Example/Example/Scenes/SafeAreaExample/SafeAreaExampleViewController.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// SafeAreaExampleViewController.swift -// Example -// -// Created by Felix Mau on 07.03.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import GradientLoadingBar -import SwiftUI -import UIKit - -final class SafeAreaExampleViewController: UIViewController { - - // MARK: - Private properties - - private let gradientLoadingBar = GradientLoadingBar(isRelativeToSafeArea: false) - private let notchGradientLoadingBar = NotchGradientLoadingBar(isRelativeToSafeArea: false) - - // MARK: - Public methods - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - - // Reset any possible visible loading bar. - gradientLoadingBar.fadeOut() - notchGradientLoadingBar.fadeOut() - } - - // MARK: - Private methods - - @IBAction private func showBasicBarButtonTouchUpInside(_: Any) { - gradientLoadingBar.fadeIn() - } - - @IBAction private func hideBasicBarButtonTouchUpInside(_: Any) { - gradientLoadingBar.fadeOut() - } - - @IBAction private func showNotchBarButtonTouchUpInside(_: Any) { - notchGradientLoadingBar.fadeIn() - } - - @IBAction private func hideNotchBarButtonTouchUpInside(_: Any) { - notchGradientLoadingBar.fadeOut() - } -} - -// MARK: - Helper - -struct SafeAreaExampleView: View { - var body: some View { - StoryboardView(name: "SafeAreaExample") - .navigationTitle("đŸ“Č Safe Area Example") - } -} diff --git a/Example/Example/Scenes/SwiftUIExample/SwiftUIExampleView.swift b/Example/Example/Scenes/SwiftUIExample/SwiftUIExampleView.swift deleted file mode 100644 index 6904d2f..0000000 --- a/Example/Example/Scenes/SwiftUIExample/SwiftUIExampleView.swift +++ /dev/null @@ -1,177 +0,0 @@ -// -// SwiftUIExampleView.swift -// Example -// -// Created by Felix Mau on 07.03.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import GradientLoadingBar -import SwiftUI - -struct SwiftUIExampleView: View { - - // MARK: - Render - - var body: some View { - List { - Section(header: Text("Default example")) { - DefaultExampleView() - } - - Section(header: Text("Custom colors")) { - CustomColorsExampleView() - } - - Section(header: Text("Custom animation duration")) { - CustomProgressDurationExampleView() - } - } - .navigationTitle("🎹 SwiftUI Example") - } -} - -// MARK: - Subviews - -private struct DefaultExampleView: View { - - @State - private var isVisible = true - - var body: some View { - VStack(spacing: 16) { - Text("Example using the default configuration.") - .frame(maxWidth: .infinity, alignment: .leading) - - GradientLoadingBarView() - .frame(maxWidth: .infinity, maxHeight: 3) - .cornerRadius(1.5) - .opacity(isVisible ? 1 : 0) - - HStack(spacing: 16) { - Button("Show") { - withAnimation(.easeInOut) { - isVisible = true - } - } - .buttonStyle(RoundedRectangleButtonStyle()) - - Button("Hide") { - withAnimation(.easeInOut) { - isVisible = false - } - } - .buttonStyle(RoundedRectangleButtonStyle()) - } - } - .padding(.horizontal, 8) - .padding(.vertical, 16) - } -} - -private struct CustomColorsExampleView: View { - - private enum Config { - /// The custom gradient colors we use. - /// Source: https://color.adobe.com/Pink-Flamingo-color-theme-10343714/ - static let gradientColors = [ - #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), - ].map(Color.init) - } - - @State - private var isVisible = true - - var body: some View { - VStack(spacing: 16) { - Text("Example showing how to provide custom gradient colors.") - .frame(maxWidth: .infinity, alignment: .leading) - - GradientLoadingBarView(gradientColors: Config.gradientColors) - .frame(maxWidth: .infinity, maxHeight: 3) - .cornerRadius(1.5) - .opacity(isVisible ? 1 : 0) - - HStack(spacing: 16) { - Button("Show") { - withAnimation(.easeInOut) { - isVisible = true - } - } - .buttonStyle(RoundedRectangleButtonStyle()) - - Button("Hide") { - withAnimation(.easeInOut) { - isVisible = false - } - } - .buttonStyle(RoundedRectangleButtonStyle()) - } - } - .padding(.horizontal, 8) - .padding(.vertical, 16) - } -} - -private struct CustomProgressDurationExampleView: View { - - private enum Config { - /// The custom progress duration to use. - static let progressDuration: TimeInterval = 10 - } - - @State - private var isVisible = true - - var body: some View { - VStack(spacing: 16) { - Text("Example showing how to provide a custom progress animation duration.") - .frame(maxWidth: .infinity, alignment: .leading) - - GradientLoadingBarView(progressDuration: Config.progressDuration) - .frame(maxWidth: .infinity, maxHeight: 3) - .cornerRadius(1.5) - .opacity(isVisible ? 1 : 0) - - HStack(spacing: 16) { - Button("Show") { - withAnimation(.easeInOut) { - isVisible = true - } - } - .buttonStyle(RoundedRectangleButtonStyle()) - - Button("Hide") { - withAnimation(.easeInOut) { - isVisible = false - } - } - .buttonStyle(RoundedRectangleButtonStyle()) - } - } - .padding(.horizontal, 8) - .padding(.vertical, 16) - } -} - -// MARK: - Helper - -private struct RoundedRectangleButtonStyle: ButtonStyle { - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .padding(12) - .frame(maxWidth: .infinity) - .foregroundColor(.white) - .background(Color.blue.cornerRadius(8)) - .scaleEffect(configuration.isPressed ? 0.95 : 1) - } -} - -// MARK: - Preview - -struct SwiftUIExampleView_Previews: PreviewProvider { - static var previews: some View { - SwiftUIExampleView() - } -} diff --git a/Example/Gemfile b/Example/Gemfile deleted file mode 100644 index 9ee8aa7..0000000 --- a/Example/Gemfile +++ /dev/null @@ -1,7 +0,0 @@ -source "https://rubygems.org" - -gem "cocoapods" -gem "fastlane" - -plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') -eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Example/Gemfile.lock b/Example/Gemfile.lock deleted file mode 100644 index b424e14..0000000 --- a/Example/Gemfile.lock +++ /dev/null @@ -1,286 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.5) - rexml - activesupport (6.1.6.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.1) - public_suffix (>= 2.0.2, < 6.0) - algoliasearch (1.27.5) - httpclient (~> 2.8, >= 2.8.3) - json (>= 1.5.1) - artifactory (3.0.15) - atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.626.0) - aws-sdk-core (3.141.0) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.525.0) - aws-sigv4 (~> 1.1) - jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.58.0) - aws-sdk-core (~> 3, >= 3.127.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.114.0) - aws-sdk-core (~> 3, >= 3.127.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.1) - aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.4) - claide (1.1.0) - cocoapods (1.11.3) - addressable (~> 2.8) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.3) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.8.0) - nap (~> 1.0) - ruby-macho (>= 1.0, < 3.0) - xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.3) - activesupport (>= 5.0, < 7) - addressable (~> 2.8) - algoliasearch (~> 1.0) - concurrent-ruby (~> 1.1) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - netrc (~> 0.11) - public_suffix (~> 4.0) - typhoeus (~> 1.0) - cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.6.3) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.1) - cocoapods-trunk (1.6.0) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.2.0) - colored (1.2) - colored2 (3.1.2) - commander (4.6.0) - highline (~> 2.0.0) - concurrent-ruby (1.1.10) - declarative (0.0.20) - digest-crc (0.6.4) - rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.8.1) - emoji_regex (3.2.3) - escape (0.0.4) - ethon (0.15.0) - ffi (>= 1.15.0) - excon (0.92.4) - faraday (1.10.2) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-cookie_jar (0.0.7) - faraday (>= 0.8.0) - http-cookie (~> 1.0.0) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - faraday_middleware (1.2.0) - faraday (~> 1.0) - fastimage (2.2.6) - fastlane (2.209.1) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.8, < 3.0.0) - artifactory (~> 3.0) - aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.3, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored - commander (~> 4.6) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 4.0) - excon (>= 0.71.0, < 1.0.0) - faraday (~> 1.0) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 1.0) - fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-apis-androidpublisher_v3 (~> 0.3) - google-apis-playcustomapp_v1 (~> 0.1) - google-cloud-storage (~> 1.31) - highline (~> 2.0) - json (< 3.0.0) - jwt (>= 2.1.0, < 3) - mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) - naturally (~> 2.2) - optparse (~> 0.1.1) - plist (>= 3.1.0, < 4.0.0) - rubyzip (>= 2.0.0, < 3.0.0) - security (= 0.1.3) - simctl (~> 1.6.3) - terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - fastlane-plugin-swiftformat (0.3.0) - ffi (1.15.5) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.25.0) - google-apis-core (>= 0.7, < 2.a) - google-apis-core (0.7.0) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) - httpclient (>= 2.8.1, < 3.a) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.a) - rexml - webrick - google-apis-iamcredentials_v1 (0.13.0) - google-apis-core (>= 0.7, < 2.a) - google-apis-playcustomapp_v1 (0.10.0) - google-apis-core (>= 0.7, < 2.a) - google-apis-storage_v1 (0.17.0) - google-apis-core (>= 0.7, < 2.a) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) - google-cloud-errors (~> 1.0) - google-cloud-env (1.6.0) - faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.2.0) - google-cloud-storage (1.39.0) - addressable (~> 2.8) - digest-crc (~> 0.4) - google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.17.0) - google-cloud-core (~> 1.6) - googleauth (>= 0.16.2, < 2.a) - mini_mime (~> 1.0) - googleauth (1.2.0) - faraday (>= 0.17.3, < 3.a) - jwt (>= 1.4, < 3.0) - memoist (~> 0.16) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (>= 0.16, < 2.a) - highline (2.0.3) - http-cookie (1.0.5) - domain_name (~> 0.5) - httpclient (2.8.3) - i18n (1.12.0) - concurrent-ruby (~> 1.0) - jmespath (1.6.1) - json (2.6.2) - jwt (2.5.0) - memoist (0.16.2) - mini_magick (4.11.0) - mini_mime (1.1.2) - minitest (5.16.3) - molinillo (0.8.0) - multi_json (1.15.0) - multipart-post (2.0.0) - nanaimo (0.3.0) - nap (1.1.0) - naturally (2.2.1) - netrc (0.11.0) - optparse (0.1.1) - os (1.1.4) - plist (3.6.0) - public_suffix (4.0.7) - rake (13.0.6) - representable (3.2.0) - declarative (< 0.1.0) - trailblazer-option (>= 0.1.1, < 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rexml (3.2.5) - rouge (2.0.7) - ruby-macho (2.5.1) - ruby2_keywords (0.0.5) - rubyzip (2.3.2) - security (0.1.3) - signet (0.17.0) - addressable (~> 2.8) - faraday (>= 0.17.5, < 3.a) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.8) - CFPropertyList - naturally - terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - trailblazer-option (0.1.2) - tty-cursor (0.7.1) - tty-screen (0.8.1) - tty-spinner (0.9.3) - tty-cursor (~> 0.7) - typhoeus (1.4.0) - ethon (>= 0.9.0) - tzinfo (2.0.5) - concurrent-ruby (~> 1.0) - uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) - unicode-display_width (1.8.0) - webrick (1.7.0) - word_wrap (1.0.0) - xcodeproj (1.22.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (~> 3.2.4) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.1) - xcpretty (~> 0.2, >= 0.0.7) - zeitwerk (2.6.0) - -PLATFORMS - ruby - -DEPENDENCIES - cocoapods - fastlane - fastlane-plugin-swiftformat - -BUNDLED WITH - 2.3.18 diff --git a/Example/GradientLoadingBarExample.xcodeproj/project.pbxproj b/Example/GradientLoadingBarExample.xcodeproj/project.pbxproj new file mode 100644 index 0000000..2db80d2 --- /dev/null +++ b/Example/GradientLoadingBarExample.xcodeproj/project.pbxproj @@ -0,0 +1,379 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + F545079B2F1283B200697C4F /* GradientLoadingBar in Frameworks */ = {isa = PBXBuildFile; productRef = F545079A2F1283B200697C4F /* GradientLoadingBar */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + F54507852F12839600697C4F /* GradientLoadingBarExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GradientLoadingBarExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + F54507872F12839600697C4F /* GradientLoadingBarExample */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = GradientLoadingBarExample; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + F54507822F12839600697C4F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F545079B2F1283B200697C4F /* GradientLoadingBar in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + F545077C2F12839600697C4F = { + isa = PBXGroup; + children = ( + F54507872F12839600697C4F /* GradientLoadingBarExample */, + F54507862F12839600697C4F /* Products */, + ); + sourceTree = ""; + }; + F54507862F12839600697C4F /* Products */ = { + isa = PBXGroup; + children = ( + F54507852F12839600697C4F /* GradientLoadingBarExample.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + F54507842F12839600697C4F /* GradientLoadingBarExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = F54507902F12839700697C4F /* Build configuration list for PBXNativeTarget "GradientLoadingBarExample" */; + buildPhases = ( + F54507812F12839600697C4F /* Sources */, + F54507822F12839600697C4F /* Frameworks */, + F54507832F12839600697C4F /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + F54507872F12839600697C4F /* GradientLoadingBarExample */, + ); + name = GradientLoadingBarExample; + packageProductDependencies = ( + F545079A2F1283B200697C4F /* GradientLoadingBar */, + ); + productName = GradientLoadingBarExample; + productReference = F54507852F12839600697C4F /* GradientLoadingBarExample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F545077D2F12839600697C4F /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2620; + LastUpgradeCheck = 2620; + TargetAttributes = { + F54507842F12839600697C4F = { + CreatedOnToolsVersion = 26.2; + }; + }; + }; + buildConfigurationList = F54507802F12839600697C4F /* Build configuration list for PBXProject "GradientLoadingBarExample" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = F545077C2F12839600697C4F; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + F54507992F1283B200697C4F /* XCLocalSwiftPackageReference "../../GradientLoadingBar" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = F54507862F12839600697C4F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F54507842F12839600697C4F /* GradientLoadingBarExample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F54507832F12839600697C4F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + F54507812F12839600697C4F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + F545078E2F12839700697C4F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 7AP949PR8S; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + F545078F2F12839700697C4F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 7AP949PR8S; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + F54507912F12839700697C4F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 7AP949PR8S; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 26.2; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 15.7; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = hamburg.felix.GradientLoadingBarExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SDKROOT = auto; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 26.2; + }; + name = Debug; + }; + F54507922F12839700697C4F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 7AP949PR8S; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 26.2; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 15.7; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = hamburg.felix.GradientLoadingBarExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SDKROOT = auto; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 26.2; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F54507802F12839600697C4F /* Build configuration list for PBXProject "GradientLoadingBarExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F545078E2F12839700697C4F /* Debug */, + F545078F2F12839700697C4F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F54507902F12839700697C4F /* Build configuration list for PBXNativeTarget "GradientLoadingBarExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F54507912F12839700697C4F /* Debug */, + F54507922F12839700697C4F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + F54507992F1283B200697C4F /* XCLocalSwiftPackageReference "../../GradientLoadingBar" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../GradientLoadingBar; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + F545079A2F1283B200697C4F /* GradientLoadingBar */ = { + isa = XCSwiftPackageProductDependency; + productName = GradientLoadingBar; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = F545077D2F12839600697C4F /* Project object */; +} diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/Example/GradientLoadingBarExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata rename to Example/GradientLoadingBarExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json b/Example/GradientLoadingBarExample/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from Example/Example/Assets.xcassets/AccentColor.colorset/Contents.json rename to Example/GradientLoadingBarExample/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/Example/GradientLoadingBarExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example/GradientLoadingBarExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..ffdfe15 --- /dev/null +++ b/Example/GradientLoadingBarExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,85 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Example/Example/Assets.xcassets/Contents.json b/Example/GradientLoadingBarExample/Assets.xcassets/Contents.json similarity index 100% rename from Example/Example/Assets.xcassets/Contents.json rename to Example/GradientLoadingBarExample/Assets.xcassets/Contents.json diff --git a/Example/GradientLoadingBarExample/ContentView.swift b/Example/GradientLoadingBarExample/ContentView.swift new file mode 100644 index 0000000..a4c0535 --- /dev/null +++ b/Example/GradientLoadingBarExample/ContentView.swift @@ -0,0 +1,40 @@ +// +// ContentView.swift +// GradientLoadingBarExample +// +// Created by Felix Mau on 10.01.26. +// Copyright © 2026 Felix Mau. All rights reserved. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + TabView { + Tab("Basic Example", systemImage: SFSymbol.iPhoneGen3.rawValue) { + BasicExampleView() + } + Tab("Notch Example", systemImage: SFSymbol.iPhoneGen2.rawValue) { + NotchExampleView() + } + Tab("UIKit Example", systemImage: SFSymbol.squareStack.rawValue) { + UIKitExampleView() + } + Tab("SwiftUI Example", systemImage: SFSymbol.swift.rawValue) { + SwiftUIExampleView() + } + } + .overlay(alignment: .top) { + Text("– Gradient Loading Bar Example –") + .font(.caption) + .offset(x: 0, y: .space3) + .accessibilityHidden(true) + } + } +} + +// MARK: - Preview + +#Preview { + ContentView() +} diff --git a/Example/GradientLoadingBarExample/Features/BasicExample/BasicExampleView.swift b/Example/GradientLoadingBarExample/Features/BasicExample/BasicExampleView.swift new file mode 100644 index 0000000..3d2dca4 --- /dev/null +++ b/Example/GradientLoadingBarExample/Features/BasicExample/BasicExampleView.swift @@ -0,0 +1,83 @@ +// +// BasicExampleView.swift +// GradientLoadingBarExample +// +// Created by Felix Mau on 10.01.26. +// Copyright © 2026 Felix Mau. All rights reserved. +// + +import GradientLoadingBar +import SwiftUI + +/// A basic example view demonstrating the usage of the `GradientLoadingBar`. +struct BasicExampleView: View { + + private let relativeToSafeAreaLoadingBar = GradientLoadingBar() + private let ignoringSafeAreaLoadingBar = GradientLoadingBar(isRelativeToSafeArea: false) + + var body: some View { + NavigationStack { + VStack(spacing: .space6) { + VStack(spacing: .space2) { + Text("Relative to Safe Area") + .font(.headline) + + HStack(spacing: .space2) { + Button { + relativeToSafeAreaLoadingBar.fadeOut() + } label: { + Label("Hide", systemImage: SFSymbol.eyeSlash.rawValue) + .padding(.space1) + } + .buttonStyle(.borderedProminent) + + Button { + relativeToSafeAreaLoadingBar.fadeIn() + } label: { + Label("Show", systemImage: SFSymbol.eye.rawValue) + .padding(.space1) + } + .buttonStyle(.borderedProminent) + } + } + + VStack(spacing: .space2) { + Text("Ignores Safe Area") + .font(.headline) + + HStack(spacing: .space2) { + Button { + ignoringSafeAreaLoadingBar.fadeOut() + } label: { + Label("Hide", systemImage: SFSymbol.eyeSlash.rawValue) + .padding(.space1) + } + .buttonStyle(.borderedProminent) + + Button { + ignoringSafeAreaLoadingBar.fadeIn() + } label: { + Label("Show", systemImage: SFSymbol.eye.rawValue) + .padding(.space1) + } + .buttonStyle(.borderedProminent) + } + } + } + .padding(.space6) + .navigationTitle("Basic Example") + } + .onDisappear { + relativeToSafeAreaLoadingBar.fadeOut() + ignoringSafeAreaLoadingBar.fadeOut() + } + } +} + +// MARK: - Preview + +#Preview { + // Previews do not provide a key window, therefore button interactions do not display the loading bar. + // To view the loading bar in action, run the example application in the simulator. + BasicExampleView() +} diff --git a/Example/GradientLoadingBarExample/Features/NotchExample/NotchExampleView.swift b/Example/GradientLoadingBarExample/Features/NotchExample/NotchExampleView.swift new file mode 100644 index 0000000..e6b41cb --- /dev/null +++ b/Example/GradientLoadingBarExample/Features/NotchExample/NotchExampleView.swift @@ -0,0 +1,58 @@ +// +// NotchExampleView.swift +// GradientLoadingBarExample +// +// Created by Felix Mau on 10.01.26. +// Copyright © 2026 Felix Mau. All rights reserved. +// + +import GradientLoadingBar +import SwiftUI + +/// An example view demonstrating the usage of the `NotchGradientLoadingBar` +struct NotchExampleView: View { + + private let notchGradientLoadingBar = NotchGradientLoadingBar() + + var body: some View { + NavigationStack { + VStack(spacing: .space4) { + HStack(spacing: .space2) { + Button { + notchGradientLoadingBar.fadeOut() + } label: { + Label("Hide", systemImage: SFSymbol.eyeSlash.rawValue) + .padding(.space1) + } + .buttonStyle(.borderedProminent) + + Button { + notchGradientLoadingBar.fadeIn() + } label: { + Label("Show", systemImage: SFSymbol.eye.rawValue) + .padding(.space1) + } + .buttonStyle(.borderedProminent) + } + + Text("**Note:** In case the device doesn't have a notch, the loading bar will be displayed with a regular layout.") + .font(.footnote) + .multilineTextAlignment(.center) + .padding(.horizontal, .space2) + } + .padding(.space6) + .navigationTitle("Notch Example") + } + .onDisappear { + notchGradientLoadingBar.fadeOut() + } + } +} + +// MARK: - Preview + +#Preview { + // Previews do not provide a key window, therefore button interactions do not display the loading bar. + // To view the loading bar in action, run the example application in the simulator. + NotchExampleView() +} diff --git a/Example/GradientLoadingBarExample/Features/SwiftUIExample/SwiftUIExampleView.swift b/Example/GradientLoadingBarExample/Features/SwiftUIExample/SwiftUIExampleView.swift new file mode 100644 index 0000000..9423528 --- /dev/null +++ b/Example/GradientLoadingBarExample/Features/SwiftUIExample/SwiftUIExampleView.swift @@ -0,0 +1,57 @@ +// +// SwiftUIExampleView.swift +// GradientLoadingBarExample +// +// Created by Felix Mau on 10.01.26. +// Copyright © 2026 Felix Mau. All rights reserved. +// + +import GradientLoadingBar +import SwiftUI + +private enum Config { + /// The custom gradient colors we use. + /// + /// Source: + static let gradientColors = [ + #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), + ].map(Color.init) +} + +/// A SwiftUI view demonstrating the usage of the `GradientLoadingBarView`, +/// showcasing both default and custom gradient colors. +struct SwiftUIExampleView: View { + var body: some View { + NavigationStack { + VStack(spacing: .space6) { + VStack(spacing: .space2) { + Text("Default Gradient Colors") + .font(.callout) + + GradientLoadingBarView() + .frame(height: 3) + .clipShape(RoundedRectangle(cornerRadius: 1.5)) + } + + VStack(spacing: .space2) { + Text("Custom Gradient Colors") + .font(.callout) + + GradientLoadingBarView( + gradientColors: Config.gradientColors, + ) + .frame(height: 3) + .clipShape(RoundedRectangle(cornerRadius: 1.5)) + } + } + .padding(.space6) + .navigationTitle("SwiftUI Example") + } + } +} + +// MARK: - Preview + +#Preview { + SwiftUIExampleView() +} diff --git a/Example/GradientLoadingBarExample/Features/UIKitExample/UIKitExampleView.swift b/Example/GradientLoadingBarExample/Features/UIKitExample/UIKitExampleView.swift new file mode 100644 index 0000000..f8c4388 --- /dev/null +++ b/Example/GradientLoadingBarExample/Features/UIKitExample/UIKitExampleView.swift @@ -0,0 +1,109 @@ +// +// UIKitExampleView.swift +// GradientLoadingBarExample +// +// Created by Felix Mau on 10.01.26. +// Copyright © 2026 Felix Mau. All rights reserved. +// + +import GradientLoadingBar +import SwiftUI +import UIKit + +private enum Config { + /// The custom gradient colors we use. + /// + /// Source: + static let gradientColors = [ + #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), + ] +} + +/// A SwiftUI view that demonstrates the usage of `GradientActivityIndicatorView` from UIKit. +struct UIKitExampleView: View { + var body: some View { + NavigationStack { + UIKitExampleViewControllerRepresentable() + .navigationTitle("UIKit Example") + } + } +} + +// MARK: - Supporting Types + +/// A UIViewControllerRepresentable that wraps the `UIKitExampleViewController`. +private struct UIKitExampleViewControllerRepresentable: UIViewControllerRepresentable { + func makeUIViewController(context _: Context) -> UIViewController { + UIKitExampleViewController() + } + + func updateUIViewController(_: UIViewController, context _: Context) {} +} + +/// A UIViewController that showcases the `GradientActivityIndicatorView` +/// with default and custom gradient colors. +private final class UIKitExampleViewController: UIViewController { + + // MARK: - Private Properties + + private let contentStackView = UIStackView() + + private let defaultGradientColorsLabel = UILabel() + private let defaultGradientActivityIndicatorView = GradientActivityIndicatorView() + + private let customGradientColorsLabel = UILabel() + private let customGradientActivityIndicatorView = GradientActivityIndicatorView() + + // MARK: - View Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + + setUpSubviews() + setUpConstraints() + } + + // MARK: - Private Methods + + private func setUpSubviews() { + contentStackView.axis = .vertical + contentStackView.alignment = .fill + contentStackView.spacing = .space2 + contentStackView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(contentStackView) + + defaultGradientColorsLabel.text = "Default Gradient Colors" + defaultGradientColorsLabel.textAlignment = .center + defaultGradientColorsLabel.font = .preferredFont(forTextStyle: .callout) + contentStackView.addArrangedSubview(defaultGradientColorsLabel) + + contentStackView.addArrangedSubview(defaultGradientActivityIndicatorView) + contentStackView.setCustomSpacing(.space6, after: defaultGradientActivityIndicatorView) + + customGradientColorsLabel.text = "Custom Gradient Colors" + customGradientColorsLabel.textAlignment = .center + customGradientColorsLabel.font = .preferredFont(forTextStyle: .callout) + contentStackView.addArrangedSubview(customGradientColorsLabel) + + customGradientActivityIndicatorView.gradientColors = Config.gradientColors + contentStackView.addArrangedSubview(customGradientActivityIndicatorView) + } + + private func setUpConstraints() { + NSLayoutConstraint.activate([ + contentStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: .space6), + contentStackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -.space6), + contentStackView.centerYAnchor.constraint(equalTo: view.centerYAnchor), + + defaultGradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: 3), + + customGradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: 3), + ]) + } +} + +// MARK: - Preview + +#Preview { + UIKitExampleView() +} diff --git a/Example/GradientLoadingBarExample/GradientLoadingBarExampleApp.swift b/Example/GradientLoadingBarExample/GradientLoadingBarExampleApp.swift new file mode 100644 index 0000000..f219cd0 --- /dev/null +++ b/Example/GradientLoadingBarExample/GradientLoadingBarExampleApp.swift @@ -0,0 +1,18 @@ +// +// GradientLoadingBarExampleApp.swift +// GradientLoadingBarExample +// +// Created by Felix Mau on 10.01.26. +// Copyright © 2026 Felix Mau. All rights reserved. +// + +import SwiftUI + +@main +struct GradientLoadingBarExampleApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/Example/GradientLoadingBarExample/Utilities/CGFloat+Spacing.swift b/Example/GradientLoadingBarExample/Utilities/CGFloat+Spacing.swift new file mode 100644 index 0000000..fdbf087 --- /dev/null +++ b/Example/GradientLoadingBarExample/Utilities/CGFloat+Spacing.swift @@ -0,0 +1,36 @@ +// +// CGFloat+Spacing.swift +// GradientLoadingBarExample +// +// Created by Felix Mau on 10.01.26. +// Copyright © 2026 Felix Mau. All rights reserved. +// + +import CoreGraphics + +/// Standard spacing values based on a 4-point grid. +/// +/// These constants provide a shared spacing scale to ensure visual consistency across layouts. +/// Values are expressed in points and increase in 4-point increments. +/// +/// Naming follows the pattern `spaceN`, where `N` represents the number of 4-point units (e.g. `space3` = 12 points). +/// +/// ### Usage +/// +/// ```swift +/// VStack(spacing: .space4) { +/// Text("Title") +/// Text("Subtitle") +/// } +/// ``` +extension CGFloat { + static let space0: CGFloat = 0 + static let space1: CGFloat = 4 + static let space2: CGFloat = 8 + static let space3: CGFloat = 12 + static let space4: CGFloat = 16 + static let space5: CGFloat = 20 + static let space6: CGFloat = 24 + static let space8: CGFloat = 32 + static let space10: CGFloat = 40 +} diff --git a/Example/GradientLoadingBarExample/Utilities/SFSymbol.swift b/Example/GradientLoadingBarExample/Utilities/SFSymbol.swift new file mode 100644 index 0000000..963de7e --- /dev/null +++ b/Example/GradientLoadingBarExample/Utilities/SFSymbol.swift @@ -0,0 +1,58 @@ +// +// SFSymbol.swift +// GradientLoadingBarExample +// +// Created by Felix Mau on 10.01.26. +// Copyright © 2026 Felix Mau. All rights reserved. +// + +import SwiftUI +import UIKit + +/// SF Symbols used by the example application. +/// +/// Using an enum avoids hard-coded string literals and improves discoverability, autocompletion and compile-time safety. +/// The raw value of each case corresponds to the SF Symbol’s system name. +enum SFSymbol: String { + case eye + case eyeSlash = "eye.slash" + case iPhoneGen2 = "iphone.gen2" + case iPhoneGen3 = "iphone.gen3" + case squareStack = "square.stack" + case swift +} + +// MARK: - Helper + +extension Image { + /// Creates a SwiftUI `Image` from an `SFSymbol`. + /// + /// ### Usage + /// + /// ```swift + /// Image(sfSymbol: .house) + /// ``` + /// + /// - Parameter sfSymbol: The SF Symbol to display. + init(sfSymbol: SFSymbol) { + self.init(systemName: sfSymbol.rawValue) + } +} + +extension UIImage { + /// Creates a `UIImage` from an `SFSymbol`. + /// + /// ### Usage + /// + /// ```swift + /// let image = UIImage(sfSymbol: .plus) + /// ``` + /// + /// - Parameter sfSymbol: The SF Symbol to display. + /// + /// - Returns: A system image, or `nil` if the symbol name is invalid or + /// unavailable on the current OS version. + convenience init?(sfSymbol: SFSymbol) { + self.init(systemName: sfSymbol.rawValue) + } +} diff --git a/Example/Podfile b/Example/Podfile deleted file mode 100644 index 7f8d190..0000000 --- a/Example/Podfile +++ /dev/null @@ -1,45 +0,0 @@ -# Fixes warning `Your project does not explicitly specify the CocoaPods master specs repo`. -source 'https://github.com/CocoaPods/Specs.git' - -platform :ios, '13.0' -use_frameworks! - -target 'Example' do - pod 'GradientLoadingBar', :path => '../', :testspecs => ['Tests'] - - # Development pods. - pod 'SwiftFormat/CLI', '~> 0.41' - pod 'SwiftLint', '~> 0.42' - pod 'SwiftConfigurationFiles', :git => 'https://github.com/fxm90/SwiftConfigurationFiles.git' - - post_install do |installer| - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - if target.name == 'GradientLoadingBar' - # Explicitly set the iOS deployment target for `GradientLoadingBar` in our pods project, as this will be read by Carthage. - # - # - Note: This value has to match `s.ios.deployment_target` from the `.podspec` file!! - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' - else - # Remove the deployment target from all other pods in our project and let them inherit - # the project/workspace deployment target that has been specified at the top of the Podfile. - # Source: https://stackoverflow.com/a/63489366 - config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' - end - end - end - - # - # Fix issues with interface builder and CocoaPods. - # - # Source: https://github.com/CocoaPods/CocoaPods/issues/7606#issuecomment-484294739 - # - installer.pods_project.build_configurations.each do |config| - next unless config.name == 'Debug' - - config.build_settings['LD_RUNPATH_SEARCH_PATHS'] = [ - '$(FRAMEWORK_SEARCH_PATHS)' - ] - end - end -end \ No newline at end of file diff --git a/Example/Podfile.lock b/Example/Podfile.lock deleted file mode 100644 index 7e097ac..0000000 --- a/Example/Podfile.lock +++ /dev/null @@ -1,43 +0,0 @@ -PODS: - - GradientLoadingBar (3.0.0) - - GradientLoadingBar/Tests (3.0.0): - - SnapshotTesting (~> 1.9) - - SnapshotTesting (1.9.0) - - SwiftConfigurationFiles (0.0.1) - - SwiftFormat/CLI (0.50.1) - - SwiftLint (0.49.1) - -DEPENDENCIES: - - GradientLoadingBar (from `../`) - - GradientLoadingBar/Tests (from `../`) - - SwiftConfigurationFiles (from `https://github.com/fxm90/SwiftConfigurationFiles.git`) - - SwiftFormat/CLI (~> 0.41) - - SwiftLint (~> 0.42) - -SPEC REPOS: - https://github.com/CocoaPods/Specs.git: - - SnapshotTesting - - SwiftFormat - - SwiftLint - -EXTERNAL SOURCES: - GradientLoadingBar: - :path: "../" - SwiftConfigurationFiles: - :git: https://github.com/fxm90/SwiftConfigurationFiles.git - -CHECKOUT OPTIONS: - SwiftConfigurationFiles: - :commit: efe01ba39069e8957fb89217b8c7fed298254691 - :git: https://github.com/fxm90/SwiftConfigurationFiles.git - -SPEC CHECKSUMS: - GradientLoadingBar: 3c4246535efdedaf9b471c05caabfdb4b87b40a2 - SnapshotTesting: 6141c48b6aa76ead61431ca665c14ab9a066c53b - SwiftConfigurationFiles: 1cf2228a911ebed9f42f8dec077bb634f04ca6c8 - SwiftFormat: e73212c71908404e333da34e303772b9e516ac9b - SwiftLint: 32ee33ded0636d0905ef6911b2b67bbaeeedafa5 - -PODFILE CHECKSUM: ea7965c23e5364bcd5cd7d3693d685af73a3e31d - -COCOAPODS: 1.11.3 diff --git a/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/AppHost-GradientLoadingBar-Unit-Tests-Info.plist b/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/AppHost-GradientLoadingBar-Unit-Tests-Info.plist deleted file mode 100644 index ee9de24..0000000 --- a/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/AppHost-GradientLoadingBar-Unit-Tests-Info.plist +++ /dev/null @@ -1,46 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - NSPrincipalClass - - UILaunchStoryboardName - LaunchScreen - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/LaunchScreen.storyboard b/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/LaunchScreen.storyboard deleted file mode 100644 index 647b86d..0000000 --- a/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/LaunchScreen.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/main.m b/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/main.m deleted file mode 100644 index 6fb6e47..0000000 --- a/Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/main.m +++ /dev/null @@ -1,30 +0,0 @@ -#import -#import - -@interface CPTestAppHostAppDelegate : UIResponder - -@property (nonatomic, strong) UIWindow *window; - -@end - -@implementation CPTestAppHostAppDelegate - -- (BOOL)application:(UIApplication *)__unused application didFinishLaunchingWithOptions:(NSDictionary *)__unused launchOptions -{ - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - self.window.rootViewController = [UIViewController new]; - - [self.window makeKeyAndVisible]; - - return YES; -} - -@end - -int main(int argc, char *argv[]) -{ - @autoreleasepool - { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([CPTestAppHostAppDelegate class])); - } -} diff --git a/Example/Pods/Local Podspecs/GradientLoadingBar.podspec.json b/Example/Pods/Local Podspecs/GradientLoadingBar.podspec.json deleted file mode 100644 index d039ad3..0000000 --- a/Example/Pods/Local Podspecs/GradientLoadingBar.podspec.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "GradientLoadingBar", - "version": "3.0.0", - "summary": "A customizable animated gradient loading bar.", - "description": "A customizable animated gradient loading bar.\nInspired by https://codepen.io/marcobiedermann/pen/LExXWW", - "homepage": "https://github.com/fxm90/GradientLoadingBar", - "screenshots": "https://raw.githubusercontent.com/fxm90/GradientLoadingBar/master/Assets/screen.gif", - "license": { - "type": "MIT", - "file": "LICENSE" - }, - "authors": { - "Felix Mau": "contact@felix.hamburg" - }, - "source": { - "git": "https://github.com/fxm90/GradientLoadingBar.git", - "tag": "3.0.0" - }, - "swift_versions": "5.5", - "platforms": { - "ios": "13.0" - }, - "source_files": "GradientLoadingBar/Sources/**/*.swift", - "testspecs": [ - { - "name": "Tests", - "test_type": "unit", - "requires_app_host": true, - "source_files": "GradientLoadingBar/Tests/**/*.{swift,md}", - "dependencies": { - "SnapshotTesting": [ - "~> 1.9" - ] - } - } - ], - "swift_version": "5.5" -} diff --git a/Example/Pods/Local Podspecs/SwiftConfigurationFiles.podspec.json b/Example/Pods/Local Podspecs/SwiftConfigurationFiles.podspec.json deleted file mode 100644 index 0091974..0000000 --- a/Example/Pods/Local Podspecs/SwiftConfigurationFiles.podspec.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "SwiftConfigurationFiles", - "version": "0.0.1", - "summary": "Repository containing configuration files for Swift development, e.g. for tools like SwiftLint or SwiftFormat.", - "homepage": "https://github.com/fxm90/Swift-Configuration-Files", - "license": { - "type": "MIT", - "file": "LICENSE" - }, - "authors": { - "Felix Mau": "me@felix.hamburg" - }, - "source": { - "git": "https://github.com/fxm90/Swift-Configuration-Files.git", - "tag": "0.0.1" - }, - "social_media_url": "https://twitter.com/_fxm90", - "preserve_paths": ".*", - "platforms": { - "osx": null, - "ios": null, - "tvos": null, - "watchos": null - } -} diff --git a/Example/Pods/Manifest.lock b/Example/Pods/Manifest.lock deleted file mode 100644 index 7e097ac..0000000 --- a/Example/Pods/Manifest.lock +++ /dev/null @@ -1,43 +0,0 @@ -PODS: - - GradientLoadingBar (3.0.0) - - GradientLoadingBar/Tests (3.0.0): - - SnapshotTesting (~> 1.9) - - SnapshotTesting (1.9.0) - - SwiftConfigurationFiles (0.0.1) - - SwiftFormat/CLI (0.50.1) - - SwiftLint (0.49.1) - -DEPENDENCIES: - - GradientLoadingBar (from `../`) - - GradientLoadingBar/Tests (from `../`) - - SwiftConfigurationFiles (from `https://github.com/fxm90/SwiftConfigurationFiles.git`) - - SwiftFormat/CLI (~> 0.41) - - SwiftLint (~> 0.42) - -SPEC REPOS: - https://github.com/CocoaPods/Specs.git: - - SnapshotTesting - - SwiftFormat - - SwiftLint - -EXTERNAL SOURCES: - GradientLoadingBar: - :path: "../" - SwiftConfigurationFiles: - :git: https://github.com/fxm90/SwiftConfigurationFiles.git - -CHECKOUT OPTIONS: - SwiftConfigurationFiles: - :commit: efe01ba39069e8957fb89217b8c7fed298254691 - :git: https://github.com/fxm90/SwiftConfigurationFiles.git - -SPEC CHECKSUMS: - GradientLoadingBar: 3c4246535efdedaf9b471c05caabfdb4b87b40a2 - SnapshotTesting: 6141c48b6aa76ead61431ca665c14ab9a066c53b - SwiftConfigurationFiles: 1cf2228a911ebed9f42f8dec077bb634f04ca6c8 - SwiftFormat: e73212c71908404e333da34e303772b9e516ac9b - SwiftLint: 32ee33ded0636d0905ef6911b2b67bbaeeedafa5 - -PODFILE CHECKSUM: ea7965c23e5364bcd5cd7d3693d685af73a3e31d - -COCOAPODS: 1.11.3 diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj deleted file mode 100644 index 212210c..0000000 --- a/Example/Pods/Pods.xcodeproj/project.pbxproj +++ /dev/null @@ -1,1711 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 55; - objects = { - -/* Begin PBXAggregateTarget section */ - 1CD0618C486973D5588EF20D2E8C0AEA /* SwiftFormat */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 6B2B7DF197DF6A7CA487150557230A41 /* Build configuration list for PBXAggregateTarget "SwiftFormat" */; - buildPhases = ( - ); - dependencies = ( - ); - name = SwiftFormat; - }; - 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */ = { - isa = PBXAggregateTarget; - buildConfigurationList = AE7B4FB01588B9E6DF09CB79FC7CE7BD /* Build configuration list for PBXAggregateTarget "SwiftLint" */; - buildPhases = ( - ); - dependencies = ( - ); - name = SwiftLint; - }; - B951B6FCD3E55AF969FBA9F7B476B877 /* SwiftConfigurationFiles */ = { - isa = PBXAggregateTarget; - buildConfigurationList = C94370DDBAF2E85B239A19BD8F0908C6 /* Build configuration list for PBXAggregateTarget "SwiftConfigurationFiles" */; - buildPhases = ( - ); - dependencies = ( - ); - name = SwiftConfigurationFiles; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 053939AEA809E304513064E25F3614FC /* Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F94052031AFA32400D582F8326AFCCA /* Codable.swift */; }; - 0A5BF68C384BA94FBC2AA21FC00A113D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA8B94E9D3B433157168D1EECCEC11CD /* Foundation.framework */; }; - 11AA9870DBA064FB6AC2D6C05EA4043D /* SpriteKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB7345B65BC2E6AEED51A68C09DDF9D3 /* SpriteKit.swift */; }; - 15FE4395AACC6ADC7A4341096C052A4A /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550C6A48FB0A4741A05944C8694352A8 /* Constants.swift */; }; - 16BE8F5F7A63A59965A2C318B044F25D /* Pods-Example-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = D6D7C498FA339E02BD53ECB8916CEA8E /* Pods-Example-dummy.m */; }; - 1D870BB7F6706FCA8AE0BD472DC9B9A4 /* GradientLoadingBarViewTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6519624624C2360C7B1023393CBE86 /* GradientLoadingBarViewTestCase.swift */; }; - 2478BA150A8D1ABEE55FAC74C17E59FC /* GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379ADEDFCD6A4441722D8A28283E8739 /* GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift */; }; - 26DB1CABC1D1A4209BF437D300F21D15 /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBC9A0B4D6CFB06A9485278F8148AD16 /* NSImage.swift */; }; - 2AA1041CDC89AA663E18DC2418FB185F /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A113998B49E270909660041C22A28B2 /* UIViewController.swift */; }; - 2B7734C9C50BBAF595D64D931B9C4FE8 /* NotchGradientLoadingBarControllerTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AE75973628059AB67B3FDC662361F71 /* NotchGradientLoadingBarControllerTestCase.swift */; }; - 2FBAB5E75D393F0B801920CDE4B74899 /* NotchGradientLoadingBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEE9E1A4383D0C413715CD83DBF635B /* NotchGradientLoadingBarViewModel.swift */; }; - 38811E3E79E0C51E8267949B862A6401 /* String+SpecialCharacters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88BFE21D2713C10409DB47F0F21882A6 /* String+SpecialCharacters.swift */; }; - 39005FDE959EFEF742B40EC4B93305FD /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EFA456CEFE923C8273B73C2B30B943E /* View.swift */; }; - 3F668C0F44DF65D9DC09F68A9377592D /* PlistEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB53F2B45CB5D7D98F03EDF1C9CA79EE /* PlistEncoder.swift */; }; - 4214C078AC5FEB970B06D75605D82BEB /* GradientLoadingBar-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BB5FF8A88BF23FA9C9AE4246CF98584 /* GradientLoadingBar-dummy.m */; }; - 42BC8C916A56AAFE426EA7EEE767DC77 /* AssertInlineSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CBBC4BFDFF7B467C1668BA81EE0EFAB /* AssertInlineSnapshot.swift */; }; - 447BCD024E0394EB29FFE20B5E239731 /* GradientActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5D9557874A5361BA0639AD2A364151 /* GradientActivityIndicatorView.swift */; }; - 453874BF1A255D67D63C20C564FD844B /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F1EA2D3AF4051ECDEF7A733ECD1F31 /* SwiftUIView.swift */; }; - 455D032F8818DD5D9D0F74115237C37B /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E7896243A385457C57299AD3B48794 /* UIImage.swift */; }; - 48D234A26F50D0501FAFE2DC619D3574 /* URLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE3DFAC03CEBE58F8287C0FCF071BD5 /* URLRequest.swift */; }; - 4D1CAF085B8A4AB40B96303C3A1BC786 /* Pods-Example-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 7825A90E082A1582EB16256B0E722B3F /* Pods-Example-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 50E35CD79B1FEE2398F8F2A3E4388899 /* SnapshotTesting-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = DBE022DB1E4E7109A78413B287A3CE2C /* SnapshotTesting-dummy.m */; }; - 51CD64623A68EF6304826AB6958E7AE1 /* GradientLoadingBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BC12A96E7B8018A28C37440814D3E9 /* GradientLoadingBarView.swift */; }; - 565E898EB0781C0832A2688C3E382443 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA8B94E9D3B433157168D1EECCEC11CD /* Foundation.framework */; }; - 5FB7653163F6C266EE63B15C86CBC18D /* Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D6F47C68158530256A4C877A272B671 /* Description.swift */; }; - 61B9ED0EF5672FE397A9ECE9C987A88B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA8B94E9D3B433157168D1EECCEC11CD /* Foundation.framework */; }; - 634706EAC8DB4714CBE3CFDE13754E71 /* CGPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4E3D8D682DA992F32DFFC3C5BB5F6C6 /* CGPath.swift */; }; - 6486606E4D936C190469E7857F2D5B3E /* NSBezierPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B2810B1B5FB3CD5EE96A56FCA48750 /* NSBezierPath.swift */; }; - 64F4C1D9998F48C0B7CBB8250A479F41 /* CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231E8C69DC0738D9232AC6A19B06C742 /* CALayer.swift */; }; - 65264C62384A1D00D8B5101058E8583D /* Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49843B6CCFBD52C4F39EA9555035F224 /* Internal.swift */; }; - 667A0A1C8B12663CADD98C9FD0A6CEDD /* SceneKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98950FFC6EBB992D35E49ADEBB822AD6 /* SceneKit.swift */; }; - 694D1A1707416FB7AD3F1938ADC5D298 /* GradientLoadingBarViewModelTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FD2F1A4D28F113D8EF502634CBECA5F /* GradientLoadingBarViewModelTestCase.swift */; }; - 6A8DF125D3A2859A195B1F69E6C97312 /* GradientLoadingBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B8AD1EB4E319274E3074E4EA5010C79 /* GradientLoadingBarViewModel.swift */; }; - 6FF2B1619BBA12103B9F5183C01764FA /* GradientLoadingBarView+ViewModelTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A47B6CB232A77E910C75D6FEA1AAA4A /* GradientLoadingBarView+ViewModelTestCase.swift */; }; - 7008482F299AF7E8B3A7A9BFACB6FE54 /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = A64E5EE1B24BD953A55E4034119A210C /* README.md */; }; - 7CF172618AE48B0946EB06DE701BB06E /* NSViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 966453F157F0E6D5DB08A2D38515749B /* NSViewController.swift */; }; - 869989422C880D5DFB1A1A85EDEC042C /* Any.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD6639600680D489F1A72928C544F6F /* Any.swift */; }; - 870A02D78217710AE43C97B8A0BC389B /* GradientActivityIndicatorView+AnimateIsHidden.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10E926B508663C7A173E887DB2A3FFB5 /* GradientActivityIndicatorView+AnimateIsHidden.swift */; }; - 8736B3D201CF21FD02B97EFBD20E63C0 /* Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F7B16214A68F20137A4AEF73266686F /* Async.swift */; }; - 8AFE90E2648DD774687E6558850E5F55 /* CaseIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1388E5A13C5D7C36F34352CC05F2E2B8 /* CaseIterable.swift */; }; - 8FB1043A06A1167870A6DAA145F491DB /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB6FFB8809A19166E02AA30ADA6F129 /* Data.swift */; }; - 90122844335989ACD9148A54CEF7C6DB /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2656F61BD2F10B00487AF6D249FFD0EA /* String.swift */; }; - 90DFFC43930C9AB601074ACD760BA42F /* NotchGradientLoadingBarViewModelTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DC75067FBFAE2101CDC04ACEEAF52A5 /* NotchGradientLoadingBarViewModelTestCase.swift */; }; - 950C48512418F6346F6BD099E2194218 /* UIBezierPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3B9909B138940F75E3FE22FFADC619 /* UIBezierPath.swift */; }; - 95ECF7C006872F5F5699C122BD0A1A13 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6322D93A5C7C2FF2358090E16BD82A0F /* UIView.swift */; }; - 9982EE05C8FF25661C9E0BE890D83B81 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA8B94E9D3B433157168D1EECCEC11CD /* Foundation.framework */; }; - A0A10801121581A1C77858185365DC26 /* SnapshotTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C882DC171D273C8FF897DFB2FE3B7CC /* SnapshotTestCase.swift */; }; - A0F1E5E62259CA69ADE34D25FC85C824 /* GradientLoadingBarControllerTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1357DCAC8C9D630923A559CF69377F /* GradientLoadingBarControllerTestCase.swift */; }; - A29CA8B5D19DE6970823549879ED8A44 /* Diff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 266337084D6D4DDB264B72BDE3CBD702 /* Diff.swift */; }; - A5EEE2181466D210351C54DB19F6599B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CA4C8194D5D065C3DB5F515FA0DDD0FB /* LaunchScreen.storyboard */; }; - A7C6EAB711158AFEA36E7DF1D77A5264 /* GradientLoadingBar-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 044B060397F0D69833748CBE684016B0 /* GradientLoadingBar-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B1547321903DB686BB403D6E6C1DFDCC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA8B94E9D3B433157168D1EECCEC11CD /* Foundation.framework */; }; - B2139D13F81B24AB00363B321F9B210B /* GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D550A12BE325CF959DC999A943C79C1B /* GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift */; }; - C27D044EB087ABBAA67E24499FF8E1D0 /* Diffing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D71792F9CE9BE2F2EA34DEADAFCD0E /* Diffing.swift */; }; - C71ABFBE24266435FE24E4E58FFA8D02 /* NSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80F8E91647A880AED7C46631E490D66B /* NSView.swift */; }; - CD72E5C540AA0BA018124E62CB359982 /* SnapshotTesting-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = DBBA25BF70EC9E43B11D1ABD4A052578 /* SnapshotTesting-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D34C28402E44804F9A2199DDC0E5ABCC /* Wait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6300A298D0C28699F231141E247B2D02 /* Wait.swift */; }; - D88240CD173FC6813C3A01C1D5A65EF8 /* GradientLoadingBarView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2F56F0A20299B6B3099D8F5A654106 /* GradientLoadingBarView+ViewModel.swift */; }; - DB8CC3E70438E372B026487E9BA8D9BE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DAE0B1C3BCC92C7420F8CE109BAB8F72 /* main.m */; }; - E15654CB61DD6B45B3D1E58E99B1155E /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EBC8F300895E39EA0DF6D6B2B5E6BCD /* XCTest.framework */; }; - E389705E05BE2B6048952E8C880E3EE3 /* GradientActivityIndicatorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBBEA2B36F48CA1D6E4CA325EA91C4C7 /* GradientActivityIndicatorViewModel.swift */; }; - E71953EF30C685953B04DC3D9A46C21C /* NotchGradientLoadingBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20AB62AFBEB6AFB56B3EEBEBA37CA07 /* NotchGradientLoadingBarController.swift */; }; - E78626B8E27AEBD9945748126E246738 /* GradientActivityIndicatorViewModelTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = F776327B63AC7738D831A067EEB9DA4C /* GradientActivityIndicatorViewModelTestCase.swift */; }; - ED8A155A563C9BCDB5160B922065F48B /* XCTAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA6FA5304879E0A8492BE211FA27D0E /* XCTAttachment.swift */; }; - EEFDC56304A876442F4003927FDF4190 /* AssertSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86FF7752DF7A138DFB23770E012AE807 /* AssertSnapshot.swift */; }; - F2DBAE1958541B2ABAB3C30EE18DEFB0 /* GradientLoadingBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD46DDFC661BED5E686F5FC283A0B473 /* GradientLoadingBarController.swift */; }; - FD08AC3299CA408DD6C7DBA603FA5FA5 /* GradientActivityIndicatorViewTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A1E1D46F4AE7109B3D37EFC6748941 /* GradientActivityIndicatorViewTestCase.swift */; }; - FF9A967D0F07BD1A3D589A92D62BDE07 /* Snapshotting.swift in Sources */ = {isa = PBXBuildFile; fileRef = D422A690C241AF646D9271C59C8DD71C /* Snapshotting.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 36AA100304E6631F98B0897A7B2B3EED /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = AD9297F9E6CC3E50EDCFECA0A77800B7; - remoteInfo = GradientLoadingBar; - }; - 4C710C00C1B5C34151BAE58AB7143354 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = AD9297F9E6CC3E50EDCFECA0A77800B7; - remoteInfo = GradientLoadingBar; - }; - 9B42ED775EFE5BBEDD6E6F3E60D310A5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 1CD0618C486973D5588EF20D2E8C0AEA; - remoteInfo = SwiftFormat; - }; - B51AC342985001CF5C57BC1B21EDA84A /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 52B60EC2A583F24ACBB69C113F5488B9; - remoteInfo = SwiftLint; - }; - DB0A50698830C469FDB2820BD788DAAB /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = B951B6FCD3E55AF969FBA9F7B476B877; - remoteInfo = SwiftConfigurationFiles; - }; - F08090CE87BB8E67E831F748574311A9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 9407360C7A4B285137243964F1612601; - remoteInfo = "AppHost-GradientLoadingBar-Unit-Tests"; - }; - F9A9F5329967A07C9C59D64CACF82695 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; - proxyType = 1; - remoteGlobalIDString = ADEAE7CDD604DE4F3072E6021A314487; - remoteInfo = SnapshotTesting; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 00160DB7CE7B9129CA2FC53486F1DC31 /* SnapshotTesting.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SnapshotTesting.release.xcconfig; sourceTree = ""; }; - 044B060397F0D69833748CBE684016B0 /* GradientLoadingBar-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GradientLoadingBar-umbrella.h"; sourceTree = ""; }; - 0A113998B49E270909660041C22A28B2 /* UIViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIViewController.swift; path = Sources/SnapshotTesting/Snapshotting/UIViewController.swift; sourceTree = ""; }; - 0AA6FA5304879E0A8492BE211FA27D0E /* XCTAttachment.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = XCTAttachment.swift; path = Sources/SnapshotTesting/Common/XCTAttachment.swift; sourceTree = ""; }; - 0B8AD1EB4E319274E3074E4EA5010C79 /* GradientLoadingBarViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GradientLoadingBarViewModel.swift; sourceTree = ""; }; - 10E926B508663C7A173E887DB2A3FFB5 /* GradientActivityIndicatorView+AnimateIsHidden.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "GradientActivityIndicatorView+AnimateIsHidden.swift"; sourceTree = ""; }; - 1376748BA48C7A6D4D0A2618DA2B2B02 /* GradientLoadingBar-Unit-Tests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GradientLoadingBar-Unit-Tests-Info.plist"; sourceTree = ""; }; - 1388E5A13C5D7C36F34352CC05F2E2B8 /* CaseIterable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CaseIterable.swift; path = Sources/SnapshotTesting/Snapshotting/CaseIterable.swift; sourceTree = ""; }; - 1BB5FF8A88BF23FA9C9AE4246CF98584 /* GradientLoadingBar-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "GradientLoadingBar-dummy.m"; sourceTree = ""; }; - 1CB6FFB8809A19166E02AA30ADA6F129 /* Data.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Data.swift; path = Sources/SnapshotTesting/Snapshotting/Data.swift; sourceTree = ""; }; - 1CBBC4BFDFF7B467C1668BA81EE0EFAB /* AssertInlineSnapshot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssertInlineSnapshot.swift; path = Sources/SnapshotTesting/AssertInlineSnapshot.swift; sourceTree = ""; }; - 1D2F56F0A20299B6B3099D8F5A654106 /* GradientLoadingBarView+ViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "GradientLoadingBarView+ViewModel.swift"; sourceTree = ""; }; - 1F667CC0E19EAF34E5A4119E2121F585 /* Pods-Example */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-Example"; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 231E8C69DC0738D9232AC6A19B06C742 /* CALayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CALayer.swift; path = Sources/SnapshotTesting/Snapshotting/CALayer.swift; sourceTree = ""; }; - 243410B9535472556EA4BB6DBC133A0D /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Example.release.xcconfig"; sourceTree = ""; }; - 2656F61BD2F10B00487AF6D249FFD0EA /* String.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = String.swift; path = Sources/SnapshotTesting/Snapshotting/String.swift; sourceTree = ""; }; - 266337084D6D4DDB264B72BDE3CBD702 /* Diff.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Diff.swift; path = Sources/SnapshotTesting/Diff.swift; sourceTree = ""; }; - 2C3B9909B138940F75E3FE22FFADC619 /* UIBezierPath.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIBezierPath.swift; path = Sources/SnapshotTesting/Snapshotting/UIBezierPath.swift; sourceTree = ""; }; - 2D37B379709695B38E68243F13211C38 /* GradientLoadingBar-Unit-Tests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "GradientLoadingBar-Unit-Tests-frameworks.sh"; sourceTree = ""; }; - 2D6F47C68158530256A4C877A272B671 /* Description.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Description.swift; path = Sources/SnapshotTesting/Snapshotting/Description.swift; sourceTree = ""; }; - 2EFA456CEFE923C8273B73C2B30B943E /* View.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = View.swift; path = Sources/SnapshotTesting/Common/View.swift; sourceTree = ""; }; - 2F7B16214A68F20137A4AEF73266686F /* Async.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Async.swift; path = Sources/SnapshotTesting/Async.swift; sourceTree = ""; }; - 301D196AEF80B82DDC3AA964412AFF48 /* SwiftLint.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftLint.release.xcconfig; sourceTree = ""; }; - 313CCB0006B673ED2F380FA1490BFDF1 /* AppHost-GradientLoadingBar-Unit-Tests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AppHost-GradientLoadingBar-Unit-Tests.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 31C1D37707DFAA5E6A164BCC07834264 /* Pods-Example-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-Example-Info.plist"; sourceTree = ""; }; - 35C305D3797C284E6F5BAA1D3E6F9BF8 /* Pods-Example.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-Example.modulemap"; sourceTree = ""; }; - 379ADEDFCD6A4441722D8A28283E8739 /* GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift"; sourceTree = ""; }; - 3A47B6CB232A77E910C75D6FEA1AAA4A /* GradientLoadingBarView+ViewModelTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "GradientLoadingBarView+ViewModelTestCase.swift"; sourceTree = ""; }; - 441854E35F81731E63E53DC7E4EEAD9D /* Pods-Example-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-Example-acknowledgements.markdown"; sourceTree = ""; }; - 45BC12A96E7B8018A28C37440814D3E9 /* GradientLoadingBarView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GradientLoadingBarView.swift; sourceTree = ""; }; - 49843B6CCFBD52C4F39EA9555035F224 /* Internal.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Internal.swift; path = Sources/SnapshotTesting/Common/Internal.swift; sourceTree = ""; }; - 4AE75973628059AB67B3FDC662361F71 /* NotchGradientLoadingBarControllerTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotchGradientLoadingBarControllerTestCase.swift; sourceTree = ""; }; - 4DC75067FBFAE2101CDC04ACEEAF52A5 /* NotchGradientLoadingBarViewModelTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotchGradientLoadingBarViewModelTestCase.swift; sourceTree = ""; }; - 550C6A48FB0A4741A05944C8694352A8 /* Constants.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; - 58D3B36D45EC14FED18273D9426BFD9D /* AppHost-GradientLoadingBar-Unit-Tests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "AppHost-GradientLoadingBar-Unit-Tests-Info.plist"; path = "Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/AppHost-GradientLoadingBar-Unit-Tests-Info.plist"; sourceTree = ""; }; - 5BEE9E1A4383D0C413715CD83DBF635B /* NotchGradientLoadingBarViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotchGradientLoadingBarViewModel.swift; sourceTree = ""; }; - 5EBC8F300895E39EA0DF6D6B2B5E6BCD /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; - 6300A298D0C28699F231141E247B2D02 /* Wait.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Wait.swift; path = Sources/SnapshotTesting/Extensions/Wait.swift; sourceTree = ""; }; - 6322D93A5C7C2FF2358090E16BD82A0F /* UIView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIView.swift; path = Sources/SnapshotTesting/Snapshotting/UIView.swift; sourceTree = ""; }; - 64D1AB601CB8942D953D27A7F77AD429 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; path = LICENSE; sourceTree = ""; }; - 67873E92210DD4E777C49C6E8AEDC108 /* GradientLoadingBar */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = GradientLoadingBar; path = GradientLoadingBar.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6C882DC171D273C8FF897DFB2FE3B7CC /* SnapshotTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SnapshotTestCase.swift; path = Sources/SnapshotTesting/SnapshotTestCase.swift; sourceTree = ""; }; - 6D6519624624C2360C7B1023393CBE86 /* GradientLoadingBarViewTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GradientLoadingBarViewTestCase.swift; sourceTree = ""; }; - 71D71792F9CE9BE2F2EA34DEADAFCD0E /* Diffing.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Diffing.swift; path = Sources/SnapshotTesting/Diffing.swift; sourceTree = ""; }; - 7825A90E082A1582EB16256B0E722B3F /* Pods-Example-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-Example-umbrella.h"; sourceTree = ""; }; - 7B5D9557874A5361BA0639AD2A364151 /* GradientActivityIndicatorView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GradientActivityIndicatorView.swift; sourceTree = ""; }; - 80F8E91647A880AED7C46631E490D66B /* NSView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSView.swift; path = Sources/SnapshotTesting/Snapshotting/NSView.swift; sourceTree = ""; }; - 83DD74C4A13D0725CA40F2810A2C3FA0 /* SwiftFormat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftFormat.debug.xcconfig; sourceTree = ""; }; - 86FF7752DF7A138DFB23770E012AE807 /* AssertSnapshot.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssertSnapshot.swift; path = Sources/SnapshotTesting/AssertSnapshot.swift; sourceTree = ""; }; - 88BFE21D2713C10409DB47F0F21882A6 /* String+SpecialCharacters.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+SpecialCharacters.swift"; path = "Sources/SnapshotTesting/Common/String+SpecialCharacters.swift"; sourceTree = ""; }; - 8C0C1AE8F5B4B54E80DF78EBE9113EFF /* GradientLoadingBar.unit-tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "GradientLoadingBar.unit-tests.release.xcconfig"; sourceTree = ""; }; - 8F234912C6C11BD0C26751D9DAFD98B0 /* SnapshotTesting-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "SnapshotTesting-Info.plist"; sourceTree = ""; }; - 8F94052031AFA32400D582F8326AFCCA /* Codable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Codable.swift; path = Sources/SnapshotTesting/Snapshotting/Codable.swift; sourceTree = ""; }; - 93B2810B1B5FB3CD5EE96A56FCA48750 /* NSBezierPath.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSBezierPath.swift; path = Sources/SnapshotTesting/Snapshotting/NSBezierPath.swift; sourceTree = ""; }; - 966453F157F0E6D5DB08A2D38515749B /* NSViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSViewController.swift; path = Sources/SnapshotTesting/Snapshotting/NSViewController.swift; sourceTree = ""; }; - 98950FFC6EBB992D35E49ADEBB822AD6 /* SceneKit.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SceneKit.swift; path = Sources/SnapshotTesting/Snapshotting/SceneKit.swift; sourceTree = ""; }; - 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - 9ED91F61AB707C323C453A88B777351A /* GradientLoadingBar-Unit-Tests-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GradientLoadingBar-Unit-Tests-prefix.pch"; sourceTree = ""; }; - 9FBD0E14174DBAA61835B53F4BE0A1C6 /* SwiftLint.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftLint.debug.xcconfig; sourceTree = ""; }; - 9FD2F1A4D28F113D8EF502634CBECA5F /* GradientLoadingBarViewModelTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GradientLoadingBarViewModelTestCase.swift; sourceTree = ""; }; - A037DBBCCACBFACB5C39A80B1C273043 /* SnapshotTesting */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = SnapshotTesting; path = SnapshotTesting.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - A34D867751E16B18A9C56E357E9B866A /* readme.md */ = {isa = PBXFileReference; includeInIndex = 1; path = readme.md; sourceTree = ""; }; - A4E3D8D682DA992F32DFFC3C5BB5F6C6 /* CGPath.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CGPath.swift; path = Sources/SnapshotTesting/Snapshotting/CGPath.swift; sourceTree = ""; }; - A5F1EA2D3AF4051ECDEF7A733ECD1F31 /* SwiftUIView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwiftUIView.swift; path = Sources/SnapshotTesting/Snapshotting/SwiftUIView.swift; sourceTree = ""; }; - A64E5EE1B24BD953A55E4034119A210C /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; path = README.md; sourceTree = ""; }; - A7E7896243A385457C57299AD3B48794 /* UIImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIImage.swift; path = Sources/SnapshotTesting/Snapshotting/UIImage.swift; sourceTree = ""; }; - A8A1E1D46F4AE7109B3D37EFC6748941 /* GradientActivityIndicatorViewTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GradientActivityIndicatorViewTestCase.swift; sourceTree = ""; }; - AB7345B65BC2E6AEED51A68C09DDF9D3 /* SpriteKit.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SpriteKit.swift; path = Sources/SnapshotTesting/Snapshotting/SpriteKit.swift; sourceTree = ""; }; - AF1357DCAC8C9D630923A559CF69377F /* GradientLoadingBarControllerTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GradientLoadingBarControllerTestCase.swift; sourceTree = ""; }; - AFECBC24E09D0D25F822C27BD944AFD4 /* Pods-Example-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-Example-frameworks.sh"; sourceTree = ""; }; - B45138496B85A072654D1D0F8EBBEDE5 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-Example.debug.xcconfig"; sourceTree = ""; }; - B8E489E08DC3F98DDD08A94BB85F95B3 /* GradientLoadingBar.unit-tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "GradientLoadingBar.unit-tests.debug.xcconfig"; sourceTree = ""; }; - BFD6639600680D489F1A72928C544F6F /* Any.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Any.swift; path = Sources/SnapshotTesting/Snapshotting/Any.swift; sourceTree = ""; }; - C79C001417A308CD3E3E9388ADA7A7DE /* SnapshotTesting-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SnapshotTesting-prefix.pch"; sourceTree = ""; }; - C8561314A3DA7D23140D2877058162B3 /* SwiftConfigurationFiles.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftConfigurationFiles.debug.xcconfig; sourceTree = ""; }; - CA4C8194D5D065C3DB5F515FA0DDD0FB /* LaunchScreen.storyboard */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = "Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/LaunchScreen.storyboard"; sourceTree = ""; }; - CA8B94E9D3B433157168D1EECCEC11CD /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; - CB525A4BD0739D1F27CFE5BF030133DC /* SwiftFormat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftFormat.release.xcconfig; sourceTree = ""; }; - CBA17F86AFC7D538904E056CA188E2B7 /* GradientLoadingBar.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GradientLoadingBar.debug.xcconfig; sourceTree = ""; }; - CBC9A0B4D6CFB06A9485278F8148AD16 /* NSImage.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NSImage.swift; path = Sources/SnapshotTesting/Snapshotting/NSImage.swift; sourceTree = ""; }; - CC830FB4E7AD67676025629098DFCB83 /* GradientLoadingBar-Unit-Tests */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "GradientLoadingBar-Unit-Tests"; path = "GradientLoadingBar-Unit-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - CD46DDFC661BED5E686F5FC283A0B473 /* GradientLoadingBarController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GradientLoadingBarController.swift; sourceTree = ""; }; - CEE3DFAC03CEBE58F8287C0FCF071BD5 /* URLRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = URLRequest.swift; path = Sources/SnapshotTesting/Snapshotting/URLRequest.swift; sourceTree = ""; }; - D20AB62AFBEB6AFB56B3EEBEBA37CA07 /* NotchGradientLoadingBarController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NotchGradientLoadingBarController.swift; sourceTree = ""; }; - D4158B7BB021CE8B5F8A2FF85BCB9287 /* GradientLoadingBar-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "GradientLoadingBar-prefix.pch"; sourceTree = ""; }; - D422A690C241AF646D9271C59C8DD71C /* Snapshotting.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Snapshotting.swift; path = Sources/SnapshotTesting/Snapshotting.swift; sourceTree = ""; }; - D550A12BE325CF959DC999A943C79C1B /* GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift"; sourceTree = ""; }; - D6D7C498FA339E02BD53ECB8916CEA8E /* Pods-Example-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-Example-dummy.m"; sourceTree = ""; }; - D9AC4E8CE11830D91FFA1CEBBE5CC167 /* SnapshotTesting.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = SnapshotTesting.modulemap; sourceTree = ""; }; - DAE0B1C3BCC92C7420F8CE109BAB8F72 /* main.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = main.m; path = "Example/Pods/AppHost-GradientLoadingBar-Unit-Tests/main.m"; sourceTree = ""; }; - DB3B2BC75CC5B008724BAA201DC6388D /* GradientLoadingBar.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = GradientLoadingBar.release.xcconfig; sourceTree = ""; }; - DBBA25BF70EC9E43B11D1ABD4A052578 /* SnapshotTesting-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "SnapshotTesting-umbrella.h"; sourceTree = ""; }; - DBE022DB1E4E7109A78413B287A3CE2C /* SnapshotTesting-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "SnapshotTesting-dummy.m"; sourceTree = ""; }; - DF87137F9667F0C25AA55E77B4977D50 /* SwiftConfigurationFiles.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftConfigurationFiles.release.xcconfig; sourceTree = ""; }; - E6197A6CB9B561CC232DFDD747127779 /* GradientLoadingBar.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = GradientLoadingBar.modulemap; sourceTree = ""; }; - EA21B344259B58996DB73382B1B1521F /* Pods-Example-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-Example-acknowledgements.plist"; sourceTree = ""; }; - EBBEA2B36F48CA1D6E4CA325EA91C4C7 /* GradientActivityIndicatorViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GradientActivityIndicatorViewModel.swift; sourceTree = ""; }; - EE38CBAA3AA7C73AA148D79A1BC4D8B0 /* GradientLoadingBar-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "GradientLoadingBar-Info.plist"; sourceTree = ""; }; - F15E3D2604499C5905C145503CF0AEF2 /* GradientLoadingBar.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = GradientLoadingBar.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; - F776327B63AC7738D831A067EEB9DA4C /* GradientActivityIndicatorViewModelTestCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GradientActivityIndicatorViewModelTestCase.swift; sourceTree = ""; }; - FB343E4C08230DD205DFE579BBED6943 /* SnapshotTesting.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SnapshotTesting.debug.xcconfig; sourceTree = ""; }; - FB53F2B45CB5D7D98F03EDF1C9CA79EE /* PlistEncoder.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PlistEncoder.swift; path = Sources/SnapshotTesting/Common/PlistEncoder.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 4B9708FC6CCCD21C03371E15235662B2 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 9982EE05C8FF25661C9E0BE890D83B81 /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 63E91520330D1271B764E6FCC5F7634E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 565E898EB0781C0832A2688C3E382443 /* Foundation.framework in Frameworks */, - E15654CB61DD6B45B3D1E58E99B1155E /* XCTest.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 9A56089B53D133A733C876ABD2AC46C5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - B1547321903DB686BB403D6E6C1DFDCC /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BEF1F637BEDBAD78C9F541DFE7D59C33 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 61B9ED0EF5672FE397A9ECE9C987A88B /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - D762B6E0A26AF53CBC4026B042C32F26 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 0A5BF68C384BA94FBC2AA21FC00A113D /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 06F693C7A39A0F5BF2B4B434087EF800 /* Support Files */ = { - isa = PBXGroup; - children = ( - 9FBD0E14174DBAA61835B53F4BE0A1C6 /* SwiftLint.debug.xcconfig */, - 301D196AEF80B82DDC3AA964412AFF48 /* SwiftLint.release.xcconfig */, - ); - name = "Support Files"; - path = "../Target Support Files/SwiftLint"; - sourceTree = ""; - }; - 1628BF05B4CAFDCC3549A101F5A10A17 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 974814B39C0012DECD183BBB91B32103 /* iOS */, - ); - name = Frameworks; - sourceTree = ""; - }; - 16461A765873BF95604C634D3908A84A /* Pod */ = { - isa = PBXGroup; - children = ( - F15E3D2604499C5905C145503CF0AEF2 /* GradientLoadingBar.podspec */, - 64D1AB601CB8942D953D27A7F77AD429 /* LICENSE */, - A34D867751E16B18A9C56E357E9B866A /* readme.md */, - ); - name = Pod; - sourceTree = ""; - }; - 1D73926BF938E676C74BBCA9AE873E96 /* Support Files */ = { - isa = PBXGroup; - children = ( - C8561314A3DA7D23140D2877058162B3 /* SwiftConfigurationFiles.debug.xcconfig */, - DF87137F9667F0C25AA55E77B4977D50 /* SwiftConfigurationFiles.release.xcconfig */, - ); - name = "Support Files"; - path = "../Target Support Files/SwiftConfigurationFiles"; - sourceTree = ""; - }; - 2E3CD5CD009CAE07E3636D51B9BA042D /* GradientLoadingBarView */ = { - isa = PBXGroup; - children = ( - 6D6519624624C2360C7B1023393CBE86 /* GradientLoadingBarViewTestCase.swift */, - ); - name = GradientLoadingBarView; - path = GradientLoadingBarView; - sourceTree = ""; - }; - 30012669746AF0B9C8295EBCC374903E /* Support Files */ = { - isa = PBXGroup; - children = ( - D9AC4E8CE11830D91FFA1CEBBE5CC167 /* SnapshotTesting.modulemap */, - DBE022DB1E4E7109A78413B287A3CE2C /* SnapshotTesting-dummy.m */, - 8F234912C6C11BD0C26751D9DAFD98B0 /* SnapshotTesting-Info.plist */, - C79C001417A308CD3E3E9388ADA7A7DE /* SnapshotTesting-prefix.pch */, - DBBA25BF70EC9E43B11D1ABD4A052578 /* SnapshotTesting-umbrella.h */, - FB343E4C08230DD205DFE579BBED6943 /* SnapshotTesting.debug.xcconfig */, - 00160DB7CE7B9129CA2FC53486F1DC31 /* SnapshotTesting.release.xcconfig */, - ); - name = "Support Files"; - path = "../Target Support Files/SnapshotTesting"; - sourceTree = ""; - }; - 3A219F4FCCE637BD1C898E78F7117601 /* Tests */ = { - isa = PBXGroup; - children = ( - 49386E682F7134B2653E64816733E73F /* SnapshotTests */, - 3D1A22DE844611AF3EF1FBD0699D459E /* UnitTests */, - ); - name = Tests; - sourceTree = ""; - }; - 3C62E73663346A4EC0083FE9F351D55E /* NotchGradientLoadingBar */ = { - isa = PBXGroup; - children = ( - D20AB62AFBEB6AFB56B3EEBEBA37CA07 /* NotchGradientLoadingBarController.swift */, - 5BEE9E1A4383D0C413715CD83DBF635B /* NotchGradientLoadingBarViewModel.swift */, - ); - name = NotchGradientLoadingBar; - path = NotchGradientLoadingBar; - sourceTree = ""; - }; - 3D1A22DE844611AF3EF1FBD0699D459E /* UnitTests */ = { - isa = PBXGroup; - children = ( - A72B86D1FD5641B2BD861B6DDC3A77B4 /* GradientActivityIndicatorView */, - 5101F5B2716AD233B91D91CE76D4FB33 /* GradientLoadingBar */, - D0E7F41DF23FE016C427D4B22E412943 /* GradientLoadingBarView */, - 41B094B7B7FD09244CD32ED739F59E7E /* NotchGradientLoadingBar */, - ); - name = UnitTests; - path = GradientLoadingBar/Tests/UnitTests; - sourceTree = ""; - }; - 41B094B7B7FD09244CD32ED739F59E7E /* NotchGradientLoadingBar */ = { - isa = PBXGroup; - children = ( - 4DC75067FBFAE2101CDC04ACEEAF52A5 /* NotchGradientLoadingBarViewModelTestCase.swift */, - ); - name = NotchGradientLoadingBar; - path = NotchGradientLoadingBar; - sourceTree = ""; - }; - 47878F2D7A0B10E123BA2F62FBB7FA9F /* Helper */ = { - isa = PBXGroup; - children = ( - 550C6A48FB0A4741A05944C8694352A8 /* Constants.swift */, - ); - name = Helper; - path = GradientLoadingBar/Sources/Helper; - sourceTree = ""; - }; - 49386E682F7134B2653E64816733E73F /* SnapshotTests */ = { - isa = PBXGroup; - children = ( - A64E5EE1B24BD953A55E4034119A210C /* README.md */, - A8650AA5ECE59E3ADFAA59700F9F8596 /* GradientActivityIndicatorView */, - B555B7835959996152A3D9311092041D /* GradientLoadingBar */, - 2E3CD5CD009CAE07E3636D51B9BA042D /* GradientLoadingBarView */, - 980B854ED8686498363F21CEFA9F9480 /* NotchGradientLoadingBar */, - ); - name = SnapshotTests; - path = GradientLoadingBar/Tests/SnapshotTests; - sourceTree = ""; - }; - 5101F5B2716AD233B91D91CE76D4FB33 /* GradientLoadingBar */ = { - isa = PBXGroup; - children = ( - 9FD2F1A4D28F113D8EF502634CBECA5F /* GradientLoadingBarViewModelTestCase.swift */, - ); - name = GradientLoadingBar; - path = GradientLoadingBar; - sourceTree = ""; - }; - 5A3D8A8DE7556A6A755A67F4958C9D5C /* GradientLoadingBar */ = { - isa = PBXGroup; - children = ( - 5C769A6B0642F48C0F1AD8DC1E9BD4CD /* AppHost-GradientLoadingBar-Unit-Tests */, - 8548E0070FC65325F1EDEB289E903A87 /* Feature */, - 47878F2D7A0B10E123BA2F62FBB7FA9F /* Helper */, - 16461A765873BF95604C634D3908A84A /* Pod */, - DE00CAB93DBE1D0B301AF7BDA8B5E1FD /* Support Files */, - 3A219F4FCCE637BD1C898E78F7117601 /* Tests */, - ); - name = GradientLoadingBar; - path = ../..; - sourceTree = ""; - }; - 5C769A6B0642F48C0F1AD8DC1E9BD4CD /* AppHost-GradientLoadingBar-Unit-Tests */ = { - isa = PBXGroup; - children = ( - 58D3B36D45EC14FED18273D9426BFD9D /* AppHost-GradientLoadingBar-Unit-Tests-Info.plist */, - CA4C8194D5D065C3DB5F515FA0DDD0FB /* LaunchScreen.storyboard */, - DAE0B1C3BCC92C7420F8CE109BAB8F72 /* main.m */, - ); - name = "AppHost-GradientLoadingBar-Unit-Tests"; - sourceTree = ""; - }; - 6083CB933269AE8EC9B95029E6B5BCD8 /* Development Pods */ = { - isa = PBXGroup; - children = ( - 5A3D8A8DE7556A6A755A67F4958C9D5C /* GradientLoadingBar */, - ); - name = "Development Pods"; - sourceTree = ""; - }; - 6A8462A6589515796672F99982F010C1 /* Products */ = { - isa = PBXGroup; - children = ( - 313CCB0006B673ED2F380FA1490BFDF1 /* AppHost-GradientLoadingBar-Unit-Tests.app */, - 67873E92210DD4E777C49C6E8AEDC108 /* GradientLoadingBar */, - CC830FB4E7AD67676025629098DFCB83 /* GradientLoadingBar-Unit-Tests */, - 1F667CC0E19EAF34E5A4119E2121F585 /* Pods-Example */, - A037DBBCCACBFACB5C39A80B1C273043 /* SnapshotTesting */, - ); - name = Products; - sourceTree = ""; - }; - 74018AB2D4B5C7057C7703888BB538CF /* GradientActivityIndicatorView */ = { - isa = PBXGroup; - children = ( - 7B5D9557874A5361BA0639AD2A364151 /* GradientActivityIndicatorView.swift */, - 10E926B508663C7A173E887DB2A3FFB5 /* GradientActivityIndicatorView+AnimateIsHidden.swift */, - EBBEA2B36F48CA1D6E4CA325EA91C4C7 /* GradientActivityIndicatorViewModel.swift */, - ); - name = GradientActivityIndicatorView; - path = GradientActivityIndicatorView; - sourceTree = ""; - }; - 80A139B451C921F970ED77952CB5C6C4 /* SwiftConfigurationFiles */ = { - isa = PBXGroup; - children = ( - 1D73926BF938E676C74BBCA9AE873E96 /* Support Files */, - ); - name = SwiftConfigurationFiles; - path = SwiftConfigurationFiles; - sourceTree = ""; - }; - 8548E0070FC65325F1EDEB289E903A87 /* Feature */ = { - isa = PBXGroup; - children = ( - 74018AB2D4B5C7057C7703888BB538CF /* GradientActivityIndicatorView */, - 9F260C9417EA57E3E5FF7C7975173CCB /* GradientLoadingBar */, - E5DE3097F4EF525463312F19971621BC /* GradientLoadingBarView */, - 3C62E73663346A4EC0083FE9F351D55E /* NotchGradientLoadingBar */, - ); - name = Feature; - path = GradientLoadingBar/Sources/Feature; - sourceTree = ""; - }; - 956EDA2F5D3FBE01915638662991ED47 /* Support Files */ = { - isa = PBXGroup; - children = ( - 83DD74C4A13D0725CA40F2810A2C3FA0 /* SwiftFormat.debug.xcconfig */, - CB525A4BD0739D1F27CFE5BF030133DC /* SwiftFormat.release.xcconfig */, - ); - name = "Support Files"; - path = "../Target Support Files/SwiftFormat"; - sourceTree = ""; - }; - 974814B39C0012DECD183BBB91B32103 /* iOS */ = { - isa = PBXGroup; - children = ( - CA8B94E9D3B433157168D1EECCEC11CD /* Foundation.framework */, - 5EBC8F300895E39EA0DF6D6B2B5E6BCD /* XCTest.framework */, - ); - name = iOS; - sourceTree = ""; - }; - 980B854ED8686498363F21CEFA9F9480 /* NotchGradientLoadingBar */ = { - isa = PBXGroup; - children = ( - 4AE75973628059AB67B3FDC662361F71 /* NotchGradientLoadingBarControllerTestCase.swift */, - ); - name = NotchGradientLoadingBar; - path = NotchGradientLoadingBar; - sourceTree = ""; - }; - 9BDBD95ED116334D1B2835202D8D3060 /* Pods-Example */ = { - isa = PBXGroup; - children = ( - 35C305D3797C284E6F5BAA1D3E6F9BF8 /* Pods-Example.modulemap */, - 441854E35F81731E63E53DC7E4EEAD9D /* Pods-Example-acknowledgements.markdown */, - EA21B344259B58996DB73382B1B1521F /* Pods-Example-acknowledgements.plist */, - D6D7C498FA339E02BD53ECB8916CEA8E /* Pods-Example-dummy.m */, - AFECBC24E09D0D25F822C27BD944AFD4 /* Pods-Example-frameworks.sh */, - 31C1D37707DFAA5E6A164BCC07834264 /* Pods-Example-Info.plist */, - 7825A90E082A1582EB16256B0E722B3F /* Pods-Example-umbrella.h */, - B45138496B85A072654D1D0F8EBBEDE5 /* Pods-Example.debug.xcconfig */, - 243410B9535472556EA4BB6DBC133A0D /* Pods-Example.release.xcconfig */, - ); - name = "Pods-Example"; - path = "Target Support Files/Pods-Example"; - sourceTree = ""; - }; - 9C29CEA8F27F07207F8DD3745BB9E281 /* SnapshotTesting */ = { - isa = PBXGroup; - children = ( - BFD6639600680D489F1A72928C544F6F /* Any.swift */, - 1CBBC4BFDFF7B467C1668BA81EE0EFAB /* AssertInlineSnapshot.swift */, - 86FF7752DF7A138DFB23770E012AE807 /* AssertSnapshot.swift */, - 2F7B16214A68F20137A4AEF73266686F /* Async.swift */, - 231E8C69DC0738D9232AC6A19B06C742 /* CALayer.swift */, - 1388E5A13C5D7C36F34352CC05F2E2B8 /* CaseIterable.swift */, - A4E3D8D682DA992F32DFFC3C5BB5F6C6 /* CGPath.swift */, - 8F94052031AFA32400D582F8326AFCCA /* Codable.swift */, - 1CB6FFB8809A19166E02AA30ADA6F129 /* Data.swift */, - 2D6F47C68158530256A4C877A272B671 /* Description.swift */, - 266337084D6D4DDB264B72BDE3CBD702 /* Diff.swift */, - 71D71792F9CE9BE2F2EA34DEADAFCD0E /* Diffing.swift */, - 49843B6CCFBD52C4F39EA9555035F224 /* Internal.swift */, - 93B2810B1B5FB3CD5EE96A56FCA48750 /* NSBezierPath.swift */, - CBC9A0B4D6CFB06A9485278F8148AD16 /* NSImage.swift */, - 80F8E91647A880AED7C46631E490D66B /* NSView.swift */, - 966453F157F0E6D5DB08A2D38515749B /* NSViewController.swift */, - FB53F2B45CB5D7D98F03EDF1C9CA79EE /* PlistEncoder.swift */, - 98950FFC6EBB992D35E49ADEBB822AD6 /* SceneKit.swift */, - 6C882DC171D273C8FF897DFB2FE3B7CC /* SnapshotTestCase.swift */, - D422A690C241AF646D9271C59C8DD71C /* Snapshotting.swift */, - AB7345B65BC2E6AEED51A68C09DDF9D3 /* SpriteKit.swift */, - 2656F61BD2F10B00487AF6D249FFD0EA /* String.swift */, - 88BFE21D2713C10409DB47F0F21882A6 /* String+SpecialCharacters.swift */, - A5F1EA2D3AF4051ECDEF7A733ECD1F31 /* SwiftUIView.swift */, - 2C3B9909B138940F75E3FE22FFADC619 /* UIBezierPath.swift */, - A7E7896243A385457C57299AD3B48794 /* UIImage.swift */, - 6322D93A5C7C2FF2358090E16BD82A0F /* UIView.swift */, - 0A113998B49E270909660041C22A28B2 /* UIViewController.swift */, - CEE3DFAC03CEBE58F8287C0FCF071BD5 /* URLRequest.swift */, - 2EFA456CEFE923C8273B73C2B30B943E /* View.swift */, - 6300A298D0C28699F231141E247B2D02 /* Wait.swift */, - 0AA6FA5304879E0A8492BE211FA27D0E /* XCTAttachment.swift */, - 30012669746AF0B9C8295EBCC374903E /* Support Files */, - ); - name = SnapshotTesting; - path = SnapshotTesting; - sourceTree = ""; - }; - 9F260C9417EA57E3E5FF7C7975173CCB /* GradientLoadingBar */ = { - isa = PBXGroup; - children = ( - CD46DDFC661BED5E686F5FC283A0B473 /* GradientLoadingBarController.swift */, - 0B8AD1EB4E319274E3074E4EA5010C79 /* GradientLoadingBarViewModel.swift */, - ); - name = GradientLoadingBar; - path = GradientLoadingBar; - sourceTree = ""; - }; - A72B86D1FD5641B2BD861B6DDC3A77B4 /* GradientActivityIndicatorView */ = { - isa = PBXGroup; - children = ( - 379ADEDFCD6A4441722D8A28283E8739 /* GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift */, - D550A12BE325CF959DC999A943C79C1B /* GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift */, - F776327B63AC7738D831A067EEB9DA4C /* GradientActivityIndicatorViewModelTestCase.swift */, - ); - name = GradientActivityIndicatorView; - path = GradientActivityIndicatorView; - sourceTree = ""; - }; - A8650AA5ECE59E3ADFAA59700F9F8596 /* GradientActivityIndicatorView */ = { - isa = PBXGroup; - children = ( - A8A1E1D46F4AE7109B3D37EFC6748941 /* GradientActivityIndicatorViewTestCase.swift */, - ); - name = GradientActivityIndicatorView; - path = GradientActivityIndicatorView; - sourceTree = ""; - }; - B555B7835959996152A3D9311092041D /* GradientLoadingBar */ = { - isa = PBXGroup; - children = ( - AF1357DCAC8C9D630923A559CF69377F /* GradientLoadingBarControllerTestCase.swift */, - ); - name = GradientLoadingBar; - path = GradientLoadingBar; - sourceTree = ""; - }; - B815371469D6FBFA912CD532BB6E046F /* Pods */ = { - isa = PBXGroup; - children = ( - 9C29CEA8F27F07207F8DD3745BB9E281 /* SnapshotTesting */, - 80A139B451C921F970ED77952CB5C6C4 /* SwiftConfigurationFiles */, - F814A96E2D8B44DF296DC92B80A7CCC4 /* SwiftFormat */, - D034FCF67A18AA80DE1892605B59A35D /* SwiftLint */, - ); - name = Pods; - sourceTree = ""; - }; - CE2E825F08D3AD0FD76E6D78D7512ED0 /* Targets Support Files */ = { - isa = PBXGroup; - children = ( - 9BDBD95ED116334D1B2835202D8D3060 /* Pods-Example */, - ); - name = "Targets Support Files"; - sourceTree = ""; - }; - CF1408CF629C7361332E53B88F7BD30C = { - isa = PBXGroup; - children = ( - 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, - 6083CB933269AE8EC9B95029E6B5BCD8 /* Development Pods */, - 1628BF05B4CAFDCC3549A101F5A10A17 /* Frameworks */, - B815371469D6FBFA912CD532BB6E046F /* Pods */, - 6A8462A6589515796672F99982F010C1 /* Products */, - CE2E825F08D3AD0FD76E6D78D7512ED0 /* Targets Support Files */, - ); - sourceTree = ""; - }; - D034FCF67A18AA80DE1892605B59A35D /* SwiftLint */ = { - isa = PBXGroup; - children = ( - 06F693C7A39A0F5BF2B4B434087EF800 /* Support Files */, - ); - name = SwiftLint; - path = SwiftLint; - sourceTree = ""; - }; - D0E7F41DF23FE016C427D4B22E412943 /* GradientLoadingBarView */ = { - isa = PBXGroup; - children = ( - 3A47B6CB232A77E910C75D6FEA1AAA4A /* GradientLoadingBarView+ViewModelTestCase.swift */, - ); - name = GradientLoadingBarView; - path = GradientLoadingBarView; - sourceTree = ""; - }; - DE00CAB93DBE1D0B301AF7BDA8B5E1FD /* Support Files */ = { - isa = PBXGroup; - children = ( - E6197A6CB9B561CC232DFDD747127779 /* GradientLoadingBar.modulemap */, - 1BB5FF8A88BF23FA9C9AE4246CF98584 /* GradientLoadingBar-dummy.m */, - EE38CBAA3AA7C73AA148D79A1BC4D8B0 /* GradientLoadingBar-Info.plist */, - D4158B7BB021CE8B5F8A2FF85BCB9287 /* GradientLoadingBar-prefix.pch */, - 044B060397F0D69833748CBE684016B0 /* GradientLoadingBar-umbrella.h */, - 2D37B379709695B38E68243F13211C38 /* GradientLoadingBar-Unit-Tests-frameworks.sh */, - 1376748BA48C7A6D4D0A2618DA2B2B02 /* GradientLoadingBar-Unit-Tests-Info.plist */, - 9ED91F61AB707C323C453A88B777351A /* GradientLoadingBar-Unit-Tests-prefix.pch */, - CBA17F86AFC7D538904E056CA188E2B7 /* GradientLoadingBar.debug.xcconfig */, - DB3B2BC75CC5B008724BAA201DC6388D /* GradientLoadingBar.release.xcconfig */, - B8E489E08DC3F98DDD08A94BB85F95B3 /* GradientLoadingBar.unit-tests.debug.xcconfig */, - 8C0C1AE8F5B4B54E80DF78EBE9113EFF /* GradientLoadingBar.unit-tests.release.xcconfig */, - ); - name = "Support Files"; - path = "Example/Pods/Target Support Files/GradientLoadingBar"; - sourceTree = ""; - }; - E5DE3097F4EF525463312F19971621BC /* GradientLoadingBarView */ = { - isa = PBXGroup; - children = ( - 45BC12A96E7B8018A28C37440814D3E9 /* GradientLoadingBarView.swift */, - 1D2F56F0A20299B6B3099D8F5A654106 /* GradientLoadingBarView+ViewModel.swift */, - ); - name = GradientLoadingBarView; - path = GradientLoadingBarView; - sourceTree = ""; - }; - F814A96E2D8B44DF296DC92B80A7CCC4 /* SwiftFormat */ = { - isa = PBXGroup; - children = ( - 956EDA2F5D3FBE01915638662991ED47 /* Support Files */, - ); - name = SwiftFormat; - path = SwiftFormat; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 25E2AFAEE0A07E311DEDB9A780D737E6 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 4D1CAF085B8A4AB40B96303C3A1BC786 /* Pods-Example-umbrella.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4C0C1F80E1645A072081144913A486F1 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - CD72E5C540AA0BA018124E62CB359982 /* SnapshotTesting-umbrella.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 663515D615CDC2F7A42200039A1C3178 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - A7C6EAB711158AFEA36E7DF1D77A5264 /* GradientLoadingBar-umbrella.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 0AEE99A309977BD12A049FF48AF9BA4B /* Pods-Example */ = { - isa = PBXNativeTarget; - buildConfigurationList = C02ADEED1DC8743D7ADDE452933B7CA4 /* Build configuration list for PBXNativeTarget "Pods-Example" */; - buildPhases = ( - 25E2AFAEE0A07E311DEDB9A780D737E6 /* Headers */, - 0D7321B7AB5E2BB6B48FB115F31E3EE3 /* Sources */, - 4B9708FC6CCCD21C03371E15235662B2 /* Frameworks */, - FFA7B40BDF131B9B8213A2F741431A3A /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - E0D46373EEAFBBAE4EF69F55F4A14518 /* PBXTargetDependency */, - 67215CC9BF92CA880BB529267A41980D /* PBXTargetDependency */, - 89FA6B65C89957D4504D1B8ECE50AB68 /* PBXTargetDependency */, - E45A6D85106708A048ABC53D317937BF /* PBXTargetDependency */, - ); - name = "Pods-Example"; - productName = Pods_Example; - productReference = 1F667CC0E19EAF34E5A4119E2121F585 /* Pods-Example */; - productType = "com.apple.product-type.framework"; - }; - 1A92A7C75080512A0168578180C192A2 /* GradientLoadingBar-Unit-Tests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 1D3F6C4419B24BF7FB3D2E922873BE3F /* Build configuration list for PBXNativeTarget "GradientLoadingBar-Unit-Tests" */; - buildPhases = ( - 6E248337DE1BC2DD115A2CCB288005F0 /* Sources */, - D762B6E0A26AF53CBC4026B042C32F26 /* Frameworks */, - F47F90893E46A98504653DF7DEA110AE /* Resources */, - 13459BDB572C6FF8C8443DB9F960625F /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 98B3F5A85E2FF67ABDFA5DC69EDC003B /* PBXTargetDependency */, - 87592E10A5FC6C0D93E64DEE3E9E3042 /* PBXTargetDependency */, - 1DB731E9A4F1C5F7BAA51C5C912498D5 /* PBXTargetDependency */, - ); - name = "GradientLoadingBar-Unit-Tests"; - productName = "GradientLoadingBar-Unit-Tests"; - productReference = CC830FB4E7AD67676025629098DFCB83 /* GradientLoadingBar-Unit-Tests */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 9407360C7A4B285137243964F1612601 /* AppHost-GradientLoadingBar-Unit-Tests */ = { - isa = PBXNativeTarget; - buildConfigurationList = C578B84345EF21DCA098765B59E20BA6 /* Build configuration list for PBXNativeTarget "AppHost-GradientLoadingBar-Unit-Tests" */; - buildPhases = ( - 0BD758FAA6F470BCD40DC7A8040373DA /* Sources */, - BEF1F637BEDBAD78C9F541DFE7D59C33 /* Frameworks */, - CBB75A513AB16DEE9B9A66364FDC1086 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "AppHost-GradientLoadingBar-Unit-Tests"; - productName = "AppHost-GradientLoadingBar-Unit-Tests"; - productReference = 313CCB0006B673ED2F380FA1490BFDF1 /* AppHost-GradientLoadingBar-Unit-Tests.app */; - productType = "com.apple.product-type.application"; - }; - AD9297F9E6CC3E50EDCFECA0A77800B7 /* GradientLoadingBar */ = { - isa = PBXNativeTarget; - buildConfigurationList = 7DCB3692213D8AF4CFA71A9A91042AEB /* Build configuration list for PBXNativeTarget "GradientLoadingBar" */; - buildPhases = ( - 663515D615CDC2F7A42200039A1C3178 /* Headers */, - 85CC367955E730FD0E114BC2A04E00F8 /* Sources */, - 9A56089B53D133A733C876ABD2AC46C5 /* Frameworks */, - CA903BB1063E9D706B0869AB25347C5B /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = GradientLoadingBar; - productName = GradientLoadingBar; - productReference = 67873E92210DD4E777C49C6E8AEDC108 /* GradientLoadingBar */; - productType = "com.apple.product-type.framework"; - }; - ADEAE7CDD604DE4F3072E6021A314487 /* SnapshotTesting */ = { - isa = PBXNativeTarget; - buildConfigurationList = D4D9657E79114CA6C13BB2F5AC6B7468 /* Build configuration list for PBXNativeTarget "SnapshotTesting" */; - buildPhases = ( - 4C0C1F80E1645A072081144913A486F1 /* Headers */, - 438AE4FDB0AA8A7C51F26849EA3745DA /* Sources */, - 63E91520330D1271B764E6FCC5F7634E /* Frameworks */, - 6334A4F0618C2FD63D9CBFB8CC894E91 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = SnapshotTesting; - productName = SnapshotTesting; - productReference = A037DBBCCACBFACB5C39A80B1C273043 /* SnapshotTesting */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - BFDFE7DC352907FC980B868725387E98 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1240; - LastUpgradeCheck = 1240; - TargetAttributes = { - 1A92A7C75080512A0168578180C192A2 = { - TestTargetID = 9407360C7A4B285137243964F1612601; - }; - }; - }; - buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; - compatibilityVersion = "Xcode 13.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - Base, - en, - ); - mainGroup = CF1408CF629C7361332E53B88F7BD30C; - productRefGroup = 6A8462A6589515796672F99982F010C1 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 9407360C7A4B285137243964F1612601 /* AppHost-GradientLoadingBar-Unit-Tests */, - AD9297F9E6CC3E50EDCFECA0A77800B7 /* GradientLoadingBar */, - 1A92A7C75080512A0168578180C192A2 /* GradientLoadingBar-Unit-Tests */, - 0AEE99A309977BD12A049FF48AF9BA4B /* Pods-Example */, - ADEAE7CDD604DE4F3072E6021A314487 /* SnapshotTesting */, - B951B6FCD3E55AF969FBA9F7B476B877 /* SwiftConfigurationFiles */, - 1CD0618C486973D5588EF20D2E8C0AEA /* SwiftFormat */, - 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 6334A4F0618C2FD63D9CBFB8CC894E91 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CA903BB1063E9D706B0869AB25347C5B /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CBB75A513AB16DEE9B9A66364FDC1086 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - A5EEE2181466D210351C54DB19F6599B /* LaunchScreen.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - F47F90893E46A98504653DF7DEA110AE /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - FFA7B40BDF131B9B8213A2F741431A3A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 13459BDB572C6FF8C8443DB9F960625F /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 0BD758FAA6F470BCD40DC7A8040373DA /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - DB8CC3E70438E372B026487E9BA8D9BE /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 0D7321B7AB5E2BB6B48FB115F31E3EE3 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 16BE8F5F7A63A59965A2C318B044F25D /* Pods-Example-dummy.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 438AE4FDB0AA8A7C51F26849EA3745DA /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 869989422C880D5DFB1A1A85EDEC042C /* Any.swift in Sources */, - 42BC8C916A56AAFE426EA7EEE767DC77 /* AssertInlineSnapshot.swift in Sources */, - EEFDC56304A876442F4003927FDF4190 /* AssertSnapshot.swift in Sources */, - 8736B3D201CF21FD02B97EFBD20E63C0 /* Async.swift in Sources */, - 64F4C1D9998F48C0B7CBB8250A479F41 /* CALayer.swift in Sources */, - 8AFE90E2648DD774687E6558850E5F55 /* CaseIterable.swift in Sources */, - 634706EAC8DB4714CBE3CFDE13754E71 /* CGPath.swift in Sources */, - 053939AEA809E304513064E25F3614FC /* Codable.swift in Sources */, - 8FB1043A06A1167870A6DAA145F491DB /* Data.swift in Sources */, - 5FB7653163F6C266EE63B15C86CBC18D /* Description.swift in Sources */, - A29CA8B5D19DE6970823549879ED8A44 /* Diff.swift in Sources */, - C27D044EB087ABBAA67E24499FF8E1D0 /* Diffing.swift in Sources */, - 65264C62384A1D00D8B5101058E8583D /* Internal.swift in Sources */, - 6486606E4D936C190469E7857F2D5B3E /* NSBezierPath.swift in Sources */, - 26DB1CABC1D1A4209BF437D300F21D15 /* NSImage.swift in Sources */, - C71ABFBE24266435FE24E4E58FFA8D02 /* NSView.swift in Sources */, - 7CF172618AE48B0946EB06DE701BB06E /* NSViewController.swift in Sources */, - 3F668C0F44DF65D9DC09F68A9377592D /* PlistEncoder.swift in Sources */, - 667A0A1C8B12663CADD98C9FD0A6CEDD /* SceneKit.swift in Sources */, - A0A10801121581A1C77858185365DC26 /* SnapshotTestCase.swift in Sources */, - 50E35CD79B1FEE2398F8F2A3E4388899 /* SnapshotTesting-dummy.m in Sources */, - FF9A967D0F07BD1A3D589A92D62BDE07 /* Snapshotting.swift in Sources */, - 11AA9870DBA064FB6AC2D6C05EA4043D /* SpriteKit.swift in Sources */, - 90122844335989ACD9148A54CEF7C6DB /* String.swift in Sources */, - 38811E3E79E0C51E8267949B862A6401 /* String+SpecialCharacters.swift in Sources */, - 453874BF1A255D67D63C20C564FD844B /* SwiftUIView.swift in Sources */, - 950C48512418F6346F6BD099E2194218 /* UIBezierPath.swift in Sources */, - 455D032F8818DD5D9D0F74115237C37B /* UIImage.swift in Sources */, - 95ECF7C006872F5F5699C122BD0A1A13 /* UIView.swift in Sources */, - 2AA1041CDC89AA663E18DC2418FB185F /* UIViewController.swift in Sources */, - 48D234A26F50D0501FAFE2DC619D3574 /* URLRequest.swift in Sources */, - 39005FDE959EFEF742B40EC4B93305FD /* View.swift in Sources */, - D34C28402E44804F9A2199DDC0E5ABCC /* Wait.swift in Sources */, - ED8A155A563C9BCDB5160B922065F48B /* XCTAttachment.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6E248337DE1BC2DD115A2CCB288005F0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 2478BA150A8D1ABEE55FAC74C17E59FC /* GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift in Sources */, - B2139D13F81B24AB00363B321F9B210B /* GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift in Sources */, - E78626B8E27AEBD9945748126E246738 /* GradientActivityIndicatorViewModelTestCase.swift in Sources */, - FD08AC3299CA408DD6C7DBA603FA5FA5 /* GradientActivityIndicatorViewTestCase.swift in Sources */, - A0F1E5E62259CA69ADE34D25FC85C824 /* GradientLoadingBarControllerTestCase.swift in Sources */, - 6FF2B1619BBA12103B9F5183C01764FA /* GradientLoadingBarView+ViewModelTestCase.swift in Sources */, - 694D1A1707416FB7AD3F1938ADC5D298 /* GradientLoadingBarViewModelTestCase.swift in Sources */, - 1D870BB7F6706FCA8AE0BD472DC9B9A4 /* GradientLoadingBarViewTestCase.swift in Sources */, - 2B7734C9C50BBAF595D64D931B9C4FE8 /* NotchGradientLoadingBarControllerTestCase.swift in Sources */, - 90DFFC43930C9AB601074ACD760BA42F /* NotchGradientLoadingBarViewModelTestCase.swift in Sources */, - 7008482F299AF7E8B3A7A9BFACB6FE54 /* README.md in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 85CC367955E730FD0E114BC2A04E00F8 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 15FE4395AACC6ADC7A4341096C052A4A /* Constants.swift in Sources */, - 447BCD024E0394EB29FFE20B5E239731 /* GradientActivityIndicatorView.swift in Sources */, - 870A02D78217710AE43C97B8A0BC389B /* GradientActivityIndicatorView+AnimateIsHidden.swift in Sources */, - E389705E05BE2B6048952E8C880E3EE3 /* GradientActivityIndicatorViewModel.swift in Sources */, - 4214C078AC5FEB970B06D75605D82BEB /* GradientLoadingBar-dummy.m in Sources */, - F2DBAE1958541B2ABAB3C30EE18DEFB0 /* GradientLoadingBarController.swift in Sources */, - 51CD64623A68EF6304826AB6958E7AE1 /* GradientLoadingBarView.swift in Sources */, - D88240CD173FC6813C3A01C1D5A65EF8 /* GradientLoadingBarView+ViewModel.swift in Sources */, - 6A8DF125D3A2859A195B1F69E6C97312 /* GradientLoadingBarViewModel.swift in Sources */, - E71953EF30C685953B04DC3D9A46C21C /* NotchGradientLoadingBarController.swift in Sources */, - 2FBAB5E75D393F0B801920CDE4B74899 /* NotchGradientLoadingBarViewModel.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 1DB731E9A4F1C5F7BAA51C5C912498D5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = SnapshotTesting; - target = ADEAE7CDD604DE4F3072E6021A314487 /* SnapshotTesting */; - targetProxy = F9A9F5329967A07C9C59D64CACF82695 /* PBXContainerItemProxy */; - }; - 67215CC9BF92CA880BB529267A41980D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = SwiftConfigurationFiles; - target = B951B6FCD3E55AF969FBA9F7B476B877 /* SwiftConfigurationFiles */; - targetProxy = DB0A50698830C469FDB2820BD788DAAB /* PBXContainerItemProxy */; - }; - 87592E10A5FC6C0D93E64DEE3E9E3042 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = GradientLoadingBar; - target = AD9297F9E6CC3E50EDCFECA0A77800B7 /* GradientLoadingBar */; - targetProxy = 4C710C00C1B5C34151BAE58AB7143354 /* PBXContainerItemProxy */; - }; - 89FA6B65C89957D4504D1B8ECE50AB68 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = SwiftFormat; - target = 1CD0618C486973D5588EF20D2E8C0AEA /* SwiftFormat */; - targetProxy = 9B42ED775EFE5BBEDD6E6F3E60D310A5 /* PBXContainerItemProxy */; - }; - 98B3F5A85E2FF67ABDFA5DC69EDC003B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "AppHost-GradientLoadingBar-Unit-Tests"; - target = 9407360C7A4B285137243964F1612601 /* AppHost-GradientLoadingBar-Unit-Tests */; - targetProxy = F08090CE87BB8E67E831F748574311A9 /* PBXContainerItemProxy */; - }; - E0D46373EEAFBBAE4EF69F55F4A14518 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = GradientLoadingBar; - target = AD9297F9E6CC3E50EDCFECA0A77800B7 /* GradientLoadingBar */; - targetProxy = 36AA100304E6631F98B0897A7B2B3EED /* PBXContainerItemProxy */; - }; - E45A6D85106708A048ABC53D317937BF /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = SwiftLint; - target = 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */; - targetProxy = B51AC342985001CF5C57BC1B21EDA84A /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 00AAD58F08A6CFDF9987F8B7D334AD29 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = CBA17F86AFC7D538904E056CA188E2B7 /* GradientLoadingBar.debug.xcconfig */; - buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/GradientLoadingBar/GradientLoadingBar-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/GradientLoadingBar/GradientLoadingBar-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MODULEMAP_FILE = "Target Support Files/GradientLoadingBar/GradientLoadingBar.modulemap"; - PRODUCT_MODULE_NAME = GradientLoadingBar; - PRODUCT_NAME = GradientLoadingBar; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5.5; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 153CA3E82B38ED7082D167ECAF573508 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; - INFOPLIST_FILE = "AppHost-GradientLoadingBar-Unit-Tests/AppHost-GradientLoadingBar-Unit-Tests-Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "AppHost-GradientLoadingBar-Unit-Tests"; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 237957FDBC9D393DA609D7ABE36B4BBF /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; - INFOPLIST_FILE = "AppHost-GradientLoadingBar-Unit-Tests/AppHost-GradientLoadingBar-Unit-Tests-Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "AppHost-GradientLoadingBar-Unit-Tests"; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 2B2DA8B0D31ED0589751719B51DA4389 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = FB343E4C08230DD205DFE579BBED6943 /* SnapshotTesting.debug.xcconfig */; - buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/SnapshotTesting/SnapshotTesting-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/SnapshotTesting/SnapshotTesting-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MODULEMAP_FILE = "Target Support Files/SnapshotTesting/SnapshotTesting.modulemap"; - PRODUCT_MODULE_NAME = SnapshotTesting; - PRODUCT_NAME = SnapshotTesting; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5.2; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 36709DC94726557E30ADF7770D0891A3 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DB3B2BC75CC5B008724BAA201DC6388D /* GradientLoadingBar.release.xcconfig */; - buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/GradientLoadingBar/GradientLoadingBar-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/GradientLoadingBar/GradientLoadingBar-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MODULEMAP_FILE = "Target Support Files/GradientLoadingBar/GradientLoadingBar.modulemap"; - PRODUCT_MODULE_NAME = GradientLoadingBar; - PRODUCT_NAME = GradientLoadingBar; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5.5; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 4062373683B4E7F5C7166110E2F5B2A9 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = C8561314A3DA7D23140D2877058162B3 /* SwiftConfigurationFiles.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 68AE94C345EE61ADF6C53BC0C5581E8F /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = B45138496B85A072654D1D0F8EBBEDE5 /* Pods-Example.debug.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-Example/Pods-Example-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-Example/Pods-Example.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 8DE5143C03248BB6CD542DE3963D6F3A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "POD_CONFIGURATION_DEBUG=1", - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(FRAMEWORK_SEARCH_PATHS)"; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRIP_INSTALLED_PRODUCT = NO; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - SYMROOT = "${SRCROOT}/../build"; - }; - name = Debug; - }; - 8DEF60E8A61723230ABCDDDD8B05F224 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = CB525A4BD0739D1F27CFE5BF030133DC /* SwiftFormat.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 9E406C6AAF85E580207CD97B0044DEAB /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_PREPROCESSOR_DEFINITIONS = ( - "POD_CONFIGURATION_RELEASE=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRIP_INSTALLED_PRODUCT = NO; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - SYMROOT = "${SRCROOT}/../build"; - }; - name = Release; - }; - A98D3D256314625B9B468A99787910F4 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = B8E489E08DC3F98DDD08A94BB85F95B3 /* GradientLoadingBar.unit-tests.debug.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGNING_ALLOWED = YES; - CODE_SIGNING_REQUIRED = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - GCC_PREFIX_HEADER = "Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-Info.plist"; - PRODUCT_NAME = "GradientLoadingBar-Unit-Tests"; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5.5; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AppHost-GradientLoadingBar-Unit-Tests.app/AppHost-GradientLoadingBar-Unit-Tests"; - }; - name = Debug; - }; - AD81E62ACCB0B7A923FC8AA288F9921E /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 301D196AEF80B82DDC3AA964412AFF48 /* SwiftLint.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - AEB44DAC8D4C8381ADAD28227CDA1D78 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 00160DB7CE7B9129CA2FC53486F1DC31 /* SnapshotTesting.release.xcconfig */; - buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREFIX_HEADER = "Target Support Files/SnapshotTesting/SnapshotTesting-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/SnapshotTesting/SnapshotTesting-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MODULEMAP_FILE = "Target Support Files/SnapshotTesting/SnapshotTesting.modulemap"; - PRODUCT_MODULE_NAME = SnapshotTesting; - PRODUCT_NAME = SnapshotTesting; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5.2; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - B4D735C278D065C9DB708F250D0B915D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 83DD74C4A13D0725CA40F2810A2C3FA0 /* SwiftFormat.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - D2E2A385931B22FAADBBF66D4C5E591D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = DF87137F9667F0C25AA55E77B4977D50 /* SwiftConfigurationFiles.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - D393D3DAB9288449DCE28E29E59F54D2 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 8C0C1AE8F5B4B54E80DF78EBE9113EFF /* GradientLoadingBar.unit-tests.release.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGNING_ALLOWED = YES; - CODE_SIGNING_REQUIRED = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - GCC_PREFIX_HEADER = "Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-prefix.pch"; - INFOPLIST_FILE = "Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-Info.plist"; - PRODUCT_NAME = "GradientLoadingBar-Unit-Tests"; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; - SWIFT_VERSION = 5.5; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AppHost-GradientLoadingBar-Unit-Tests.app/AppHost-GradientLoadingBar-Unit-Tests"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - EADD1F50ABC8096A0D6CB18822BB4EE4 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9FBD0E14174DBAA61835B53F4BE0A1C6 /* SwiftLint.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - EFAB7B60C35CB9C7AE96F1D6CD5FCDCE /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 243410B9535472556EA4BB6DBC133A0D /* Pods-Example.release.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; - CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "Target Support Files/Pods-Example/Pods-Example-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MACH_O_TYPE = staticlib; - MODULEMAP_FILE = "Target Support Files/Pods-Example/Pods-Example.modulemap"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SDKROOT = iphoneos; - SKIP_INSTALL = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 1D3F6C4419B24BF7FB3D2E922873BE3F /* Build configuration list for PBXNativeTarget "GradientLoadingBar-Unit-Tests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - A98D3D256314625B9B468A99787910F4 /* Debug */, - D393D3DAB9288449DCE28E29E59F54D2 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 8DE5143C03248BB6CD542DE3963D6F3A /* Debug */, - 9E406C6AAF85E580207CD97B0044DEAB /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 6B2B7DF197DF6A7CA487150557230A41 /* Build configuration list for PBXAggregateTarget "SwiftFormat" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B4D735C278D065C9DB708F250D0B915D /* Debug */, - 8DEF60E8A61723230ABCDDDD8B05F224 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 7DCB3692213D8AF4CFA71A9A91042AEB /* Build configuration list for PBXNativeTarget "GradientLoadingBar" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 00AAD58F08A6CFDF9987F8B7D334AD29 /* Debug */, - 36709DC94726557E30ADF7770D0891A3 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AE7B4FB01588B9E6DF09CB79FC7CE7BD /* Build configuration list for PBXAggregateTarget "SwiftLint" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - EADD1F50ABC8096A0D6CB18822BB4EE4 /* Debug */, - AD81E62ACCB0B7A923FC8AA288F9921E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C02ADEED1DC8743D7ADDE452933B7CA4 /* Build configuration list for PBXNativeTarget "Pods-Example" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 68AE94C345EE61ADF6C53BC0C5581E8F /* Debug */, - EFAB7B60C35CB9C7AE96F1D6CD5FCDCE /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C578B84345EF21DCA098765B59E20BA6 /* Build configuration list for PBXNativeTarget "AppHost-GradientLoadingBar-Unit-Tests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 153CA3E82B38ED7082D167ECAF573508 /* Debug */, - 237957FDBC9D393DA609D7ABE36B4BBF /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - C94370DDBAF2E85B239A19BD8F0908C6 /* Build configuration list for PBXAggregateTarget "SwiftConfigurationFiles" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4062373683B4E7F5C7166110E2F5B2A9 /* Debug */, - D2E2A385931B22FAADBBF66D4C5E591D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - D4D9657E79114CA6C13BB2F5AC6B7468 /* Build configuration list for PBXNativeTarget "SnapshotTesting" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 2B2DA8B0D31ED0589751719B51DA4389 /* Debug */, - AEB44DAC8D4C8381ADAD28227CDA1D78 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; -} diff --git a/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/GradientLoadingBar-Unit-Tests.xcscheme b/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/GradientLoadingBar-Unit-Tests.xcscheme deleted file mode 100644 index b275cd0..0000000 --- a/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/GradientLoadingBar-Unit-Tests.xcscheme +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/GradientLoadingBar.xcscheme b/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/GradientLoadingBar.xcscheme deleted file mode 100644 index 5d01e55..0000000 --- a/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/GradientLoadingBar.xcscheme +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example/Pods/SnapshotTesting/LICENSE b/Example/Pods/SnapshotTesting/LICENSE deleted file mode 100644 index cb545c0..0000000 --- a/Example/Pods/SnapshotTesting/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Point-Free, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Example/Pods/SnapshotTesting/README.md b/Example/Pods/SnapshotTesting/README.md deleted file mode 100644 index 6382483..0000000 --- a/Example/Pods/SnapshotTesting/README.md +++ /dev/null @@ -1,244 +0,0 @@ -# 📾 SnapshotTesting - -[![Swift 5.1](https://img.shields.io/badge/swift-5.1-ED523F.svg?style=flat)](https://swift.org/download/) -[![CI](https://github.com/pointfreeco/swift-snapshot-testing/workflows/CI/badge.svg)](https://actions-badge.atrox.dev/pointfreeco/swift-snapshot-testing/goto) -[![@pointfreeco](https://img.shields.io/badge/contact-@pointfreeco-5AA9E7.svg?style=flat)](https://twitter.com/pointfreeco) - -Delightful Swift snapshot testing. - - - -## Usage - -Once [installed](#installation), _no additional configuration is required_. You can import the `SnapshotTesting` module and call the `assertSnapshot` function. - -``` swift -import SnapshotTesting -import XCTest - -class MyViewControllerTests: XCTestCase { - func testMyViewController() { - let vc = MyViewController() - - assertSnapshot(matching: vc, as: .image) - } -} -``` - -When an assertion first runs, a snapshot is automatically recorded to disk and the test will fail, printing out the file path of any newly-recorded reference. - -> 🛑 failed - No reference was found on disk. Automatically recorded snapshot: 
 -> -> open "
/MyAppTests/\_\_Snapshots\_\_/MyViewControllerTests/testMyViewController.png" -> -> Re-run "testMyViewController" to test against the newly-recorded snapshot. - -Repeat test runs will load this reference and compare it with the runtime value. If they don't match, the test will fail and describe the difference. Failures can be inspected from Xcode's Report Navigator or by inspecting the file URLs of the failure. - -You can record a new reference by setting the `record` parameter to `true` on the assertion or setting `isRecording` globally. - -``` swift -assertSnapshot(matching: vc, as: .image, record: true) - -// or globally - -isRecording = true -assertSnapshot(matching: vc, as: .image) -``` - -## Snapshot Anything - -While most snapshot testing libraries in the Swift community are limited to `UIImage`s of `UIView`s, SnapshotTesting can work with _any_ format of _any_ value on _any_ Swift platform! - -The `assertSnapshot` function accepts a value and any snapshot strategy that value supports. This means that a [view](Documentation/Available-Snapshot-Strategies.md#uiview) or [view controller](Documentation/Available-Snapshot-Strategies.md#uiviewcontroller) can be tested against an image representation _and_ against a textual representation of its properties and subview hierarchy. - -``` swift -assertSnapshot(matching: vc, as: .image) -assertSnapshot(matching: vc, as: .recursiveDescription) -``` - -View testing is [highly configurable](Documentation/Available-Snapshot-Strategies.md#uiviewcontroller). You can override trait collections (for specific size classes and content size categories) and generate device-agnostic snapshots, all from a single simulator. - -``` swift -assertSnapshot(matching: vc, as: .image(on: .iPhoneSe)) -assertSnapshot(matching: vc, as: .recursiveDescription(on: .iPhoneSe)) - -assertSnapshot(matching: vc, as: .image(on: .iPhoneSe(.landscape))) -assertSnapshot(matching: vc, as: .recursiveDescription(on: .iPhoneSe(.landscape))) - -assertSnapshot(matching: vc, as: .image(on: .iPhoneX)) -assertSnapshot(matching: vc, as: .recursiveDescription(on: .iPhoneX)) - -assertSnapshot(matching: vc, as: .image(on: .iPadMini(.portrait))) -assertSnapshot(matching: vc, as: .recursiveDescription(on: .iPadMini(.portrait))) -``` - -> ⚠ Warning: Snapshots must be compared using a simulator with the same OS, device gamut, and scale as the simulator that originally took the reference to avoid discrepancies between images. - -Better yet, SnapshotTesting isn't limited to views and view controllers! There are [a number of available snapshot strategies](Documentation/Available-Snapshot-Strategies.md) to choose from. - -For example, you can snapshot test URL requests (_e.g._, those that your API client prepares). - -``` swift -assertSnapshot(matching: urlRequest, as: .raw) -// POST http://localhost:8080/account -// Cookie: pf_session={"userId":"1"} -// -// email=blob%40pointfree.co&name=Blob -``` - -And you can snapshot test `Encodable` values against their JSON _and_ property list representations. - -``` swift -assertSnapshot(matching: user, as: .json) -// { -// "bio" : "Blobbed around the world.", -// "id" : 1, -// "name" : "Blobby" -// } - -assertSnapshot(matching: user, as: .plist) -// -// -// -// -// bio -// Blobbed around the world. -// id -// 1 -// name -// Blobby -// -// -``` - -In fact, _[any](Documentation/Available-Snapshot-Strategies.md#any)_ value can be snapshot-tested by default using its [mirror](https://developer.apple.com/documentation/swift/mirror)! - -``` swift -assertSnapshot(matching: user, as: .dump) -// ▿ User -// - bio: "Blobbed around the world." -// - id: 1 -// - name: "Blobby" -``` - -If your data can be represented as an image, text, or data, you can write a snapshot test for it! Check out [all of the snapshot strategies](Documentation/Available-Snapshot-Strategies.md) that ship with SnapshotTesting and [learn how to define your own custom strategies](Documentation/Defining-Custom-Snapshot-Strategies.md). - -## Installation - -### Xcode 11 - -> ⚠ Warning: By default, Xcode will try to add the SnapshotTesting package to your project's main application/framework target. Please ensure that SnapshotTesting is added to a _test_ target instead, as documented in the last step, below. - - 1. From the **File** menu, navigate through **Swift Packages** and select **Add Package Dependency
**. - 2. Enter package repository URL: `https://github.com/pointfreeco/swift-snapshot-testing.git` - 3. Confirm the version and let Xcode resolve the package - 4. On the final dialog, update SnapshotTesting's **Add to Target** column to a test target that will contain snapshot tests (if you have more than one test target, you can later add SnapshotTesting to them by manually linking the library in its build phase) - -### Swift Package Manager - -If you want to use SnapshotTesting in any other project that uses [SwiftPM](https://swift.org/package-manager/), add the package as a dependency in `Package.swift`: - -```swift -dependencies: [ - .package(name: "SnapshotTesting", url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.8.1"), -] -``` - -Next, add `SnapshotTesting` as a dependency of your test target: - -```swift -targets: [ - .target(name: "MyApp", dependencies: [], path: "Sources"), - .testTarget(name: "MyAppTests", dependencies: ["MyApp", "SnapshotTesting"]) -] -``` - -### Carthage - -If you use [Carthage](https://github.com/Carthage/Carthage), you can add the following dependency to your `Cartfile`: - -``` ruby -github "pointfreeco/swift-snapshot-testing" ~> 1.8.0 -``` - -> ⚠ Warning: Carthage instructs you to drag frameworks into your Xcode project. Xcode may automatically attempt to link these frameworks to your app target. `SnapshotTesting.framework` is only compatible with test targets, so when you first add it to your project: -> -> 1. Remove `SnapshotTesting.framework` from any non-test target it may have been added to. -> 2. Add `SnapshotTesting.framework` to any applicable test targets. -> 3. Add a **New Copy Build Phase** to any applicable test targets with **Destination** set to "Frameworks", and add `SnapshotTesting.framework` as an item to this phase. -> 4. Do _not_ add `SnapshotTesting.framework` to the "Input Files" or "Output Files" of your app target's Carthage `copy-frameworks` **Run Script Phase**. -> -> See Carthage's "[Adding frameworks to unit tests or a framework](https://github.com/Carthage/Carthage#adding-frameworks-to-unit-tests-or-a-framework)" documentation for more. - -### CocoaPods - -If your project uses [CocoaPods](https://cocoapods.org), add the pod to any applicable test targets in your `Podfile`: - -```ruby -target 'MyAppTests' do - pod 'SnapshotTesting', '~> 1.8.1' -end -``` - -## Features - - - [**Dozens of snapshot strategies**](Documentation/Available-Snapshot-Strategies.md). Snapshot testing isn't just for `UIView`s and `CALayer`s. Write snapshots against _any_ value. - - [**Write your own snapshot strategies**](Documentation/Defining-Custom-Snapshot-Strategies.md). If you can convert it to an image, string, data, or your own diffable format, you can snapshot test it! Build your own snapshot strategies from scratch or transform existing ones. - - **No configuration required.** Don't fuss with scheme settings and environment variables. Snapshots are automatically saved alongside your tests. - - **More hands-off.** New snapshots are recorded whether `isRecording` mode is `true` or not. - - **Subclass-free.** Assert from any XCTest case or Quick spec. - - **Device-agnostic snapshots.** Render views and view controllers for specific devices and trait collections from a single simulator. - - **First-class Xcode support.** Image differences are captured as XCTest attachments. Text differences are rendered in inline error messages. - - **Supports any platform that supports Swift.** Write snapshot tests for iOS, Linux, macOS, and tvOS. - - **SceneKit, SpriteKit, and WebKit support.** Most snapshot testing libraries don't support these view subclasses. - - **`Codable` support**. Snapshot encodable data structures into their [JSON](Documentation/Available-Snapshot-Strategies.md#json) and [property list](Documentation/Available-Snapshot-Strategies.md#plist) representations. - - **Custom diff tool integration**. - -## Plug-ins - - - [swift-snapshot-testing-nimble](https://github.com/Killectro/swift-snapshot-testing-nimble) adds [Nimble](https://github.com/Quick/Nimble) matchers for SnapshotTesting. - - - [swift-html](https://github.com/pointfreeco/swift-html) is a Swift DSL for type-safe, extensible, and transformable HTML documents and includes an `HtmlSnapshotTesting` module to snapshot test its HTML documents. - - - [GRDBSnapshotTesting](https://github.com/SebastianOsinski/GRDBSnapshotTesting) adds snapshot strategy for testing SQLite database migrations made with [GRDB](https://github.com/groue/GRDB.swift). - - - [AccessibilitySnapshot](https://github.com/cashapp/AccessibilitySnapshot) adds easy regression testing for iOS accessibility. - - - [AccessibilitySnapshotColorBlindness](https://github.com/Sherlouk/AccessibilitySnapshotColorBlindness) adds snapshot strategies for color blindness simulation on iOS views, view controllers and images. - -Have you written your own SnapshotTesting plug-in? [Add it here](https://github.com/pointfreeco/swift-snapshot-testing/edit/master/README.md) and submit a pull request! - -## Related Tools - - - [`iOSSnapshotTestCase`](https://github.com/uber/ios-snapshot-test-case/) helped introduce screen shot testing to a broad audience in the iOS community. Experience with it inspired the creation of this library. - - - [Jest](https://jestjs.io) brought generalized snapshot testing to the JavaScript community with a polished user experience. Several features of this library (diffing, automatically capturing new snapshots) were directly influenced. - -## Learn More - -SnapshotTesting was designed with [witness-oriented programming](https://www.pointfree.co/episodes/ep39-witness-oriented-library-design). - -This concept (and more) are explored thoroughly in a series of episodes on [Point-Free](https://www.pointfree.co), a video series exploring functional programming and Swift hosted by [Brandon Williams](https://twitter.com/mbrandonw) and [Stephen Celis](https://twitter.com/stephencelis). - -Witness-oriented programming and the design of this library was explored in the following [Point-Free](https://www.pointfree.co) episodes: - - - [Episode 33](https://www.pointfree.co/episodes/ep33-protocol-witnesses-part-1): Protocol Witnesses: Part 1 - - [Episode 34](https://www.pointfree.co/episodes/ep34-protocol-witnesses-part-1): Protocol Witnesses: Part 2 - - [Episode 35](https://www.pointfree.co/episodes/ep35-advanced-protocol-witnesses-part-1): Advanced Protocol Witnesses: Part 1 - - [Episode 36](https://www.pointfree.co/episodes/ep36-advanced-protocol-witnesses-part-2): Advanced Protocol Witnesses: Part 2 - - [Episode 37](https://www.pointfree.co/episodes/ep37-protocol-oriented-library-design-part-1): Protocol-Oriented Library Design: Part 1 - - [Episode 38](https://www.pointfree.co/episodes/ep38-protocol-oriented-library-design-part-2): Protocol-Oriented Library Design: Part 2 - - [Episode 39](https://www.pointfree.co/episodes/ep39-witness-oriented-library-design): Witness-Oriented Library Design - - [Episode 40](https://www.pointfree.co/episodes/ep40-async-functional-refactoring): Async Functional Refactoring - - [Episode 41](https://www.pointfree.co/episodes/ep41-a-tour-of-snapshot-testing): A Tour of Snapshot Testing 🆓 - - - video poster image - - -## License - -This library is released under the MIT license. See [LICENSE](LICENSE) for details. diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/AssertInlineSnapshot.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/AssertInlineSnapshot.swift deleted file mode 100644 index 434cbfd..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/AssertInlineSnapshot.swift +++ /dev/null @@ -1,326 +0,0 @@ -import XCTest - -/// Asserts that a given value matches a string literal. -/// -/// Note: Empty `reference` will be replaced automatically with generated output. -/// -/// Usage: -/// ``` -/// _assertInlineSnapshot(matching: value, as: .dump, with: """ -/// """) -/// ``` -/// -/// - Parameters: -/// - value: A value to compare against a reference. -/// - snapshotting: A strategy for serializing, deserializing, and comparing values. -/// - recording: Whether or not to record a new reference. -/// - timeout: The amount of time a snapshot must be generated in. -/// - reference: The expected output of snapshotting. -/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called. -/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called. -public func _assertInlineSnapshot( - matching value: @autoclosure () throws -> Value, - as snapshotting: Snapshotting, - record recording: Bool = false, - timeout: TimeInterval = 5, - with reference: String, - file: StaticString = #file, - testName: String = #function, - line: UInt = #line - ) { - - let failure = _verifyInlineSnapshot( - matching: try value(), - as: snapshotting, - record: recording, - timeout: timeout, - with: reference, - file: file, - testName: testName, - line: line - ) - guard let message = failure else { return } - XCTFail(message, file: file, line: line) -} - -/// Verifies that a given value matches a string literal. -/// -/// Third party snapshot assert helpers can be built on top of this function. Simply invoke `verifyInlineSnapshot` with your own arguments, and then invoke `XCTFail` with the string returned if it is non-`nil`. -/// -/// - Parameters: -/// - value: A value to compare against a reference. -/// - snapshotting: A strategy for serializing, deserializing, and comparing values. -/// - recording: Whether or not to record a new reference. -/// - timeout: The amount of time a snapshot must be generated in. -/// - reference: The expected output of snapshotting. -/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called. -/// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called. -/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called. -/// - Returns: A failure message or, if the value matches, nil. -public func _verifyInlineSnapshot( - matching value: @autoclosure () throws -> Value, - as snapshotting: Snapshotting, - record recording: Bool = false, - timeout: TimeInterval = 5, - with reference: String, - file: StaticString = #file, - testName: String = #function, - line: UInt = #line - ) - -> String? { - - let recording = recording || isRecording - - do { - let tookSnapshot = XCTestExpectation(description: "Took snapshot") - var optionalDiffable: String? - snapshotting.snapshot(try value()).run { b in - optionalDiffable = b - tookSnapshot.fulfill() - } - let result = XCTWaiter.wait(for: [tookSnapshot], timeout: timeout) - switch result { - case .completed: - break - case .timedOut: - return """ - Exceeded timeout of \(timeout) seconds waiting for snapshot. - - This can happen when an asynchronously rendered view (like a web view) has not loaded. \ - Ensure that every subview of the view hierarchy has loaded to avoid timeouts, or, if a \ - timeout is unavoidable, consider setting the "timeout" parameter of "assertSnapshot" to \ - a higher value. - """ - case .incorrectOrder, .invertedFulfillment, .interrupted: - return "Couldn't snapshot value" - @unknown default: - return "Couldn't snapshot value" - } - - let trimmingChars = CharacterSet.whitespacesAndNewlines.union(CharacterSet(charactersIn: "\u{FEFF}")) - guard let diffable = optionalDiffable?.trimmingCharacters(in: trimmingChars) else { - return "Couldn't snapshot value" - } - - let trimmedReference = reference.trimmingCharacters(in: .whitespacesAndNewlines) - - // Always perform diff, and return early on success! - guard let (failure, attachments) = snapshotting.diffing.diff(trimmedReference, diffable) else { - return nil - } - - // If that diff failed, we either record or fail. - if recording || trimmedReference.isEmpty { - let fileName = "\(file)" - let sourceCodeFilePath = URL(fileURLWithPath: fileName, isDirectory: false) - let sourceCode = try String(contentsOf: sourceCodeFilePath) - var newRecordings = recordings - - let modifiedSource = try writeInlineSnapshot( - &newRecordings, - Context( - sourceCode: sourceCode, - diffable: diffable, - fileName: fileName, - lineIndex: Int(line) - ) - ).sourceCode - - try modifiedSource - .data(using: String.Encoding.utf8)? - .write(to: sourceCodeFilePath) - - if newRecordings != recordings { - recordings = newRecordings - /// If no other recording has been made, then fail! - return """ - No reference was found inline. Automatically recorded snapshot. - - Re-run "\(sanitizePathComponent(testName))" to test against the newly-recorded snapshot. - """ - } else { - /// There is already an failure in this file, - /// and we don't want to write to the wrong place. - return nil - } - } - - /// Did not successfully record, so we will fail. - if !attachments.isEmpty { - #if !os(Linux) - if ProcessInfo.processInfo.environment.keys.contains("__XCODE_BUILT_PRODUCTS_DIR_PATHS") { - XCTContext.runActivity(named: "Attached Failure Diff") { activity in - attachments.forEach { - activity.add($0) - } - } - } - #endif - } - - return """ - Snapshot does not match reference. - - \(failure.trimmingCharacters(in: .whitespacesAndNewlines)) - """ - - } catch { - return error.localizedDescription - } -} - -internal typealias Recordings = [String: [FileRecording]] - -internal struct Context { - let sourceCode: String - let diffable: String - let fileName: String - // First line of a file is line 1 (as with the #line macro) - let lineIndex: Int - - func setSourceCode(_ newSourceCode: String) -> Context { - return Context( - sourceCode: newSourceCode, - diffable: diffable, - fileName: fileName, - lineIndex: lineIndex - ) - } -} - -internal func writeInlineSnapshot( - _ recordings: inout Recordings, - _ context: Context -) throws -> Context { - var sourceCodeLines = context.sourceCode - .split(separator: "\n", omittingEmptySubsequences: false) - - let otherRecordings = recordings[context.fileName, default: []] - let otherRecordingsAboveThisLine = otherRecordings.filter { $0.line < context.lineIndex } - let offsetStartIndex = otherRecordingsAboveThisLine.reduce(context.lineIndex) { $0 + $1.difference } - let functionLineIndex = offsetStartIndex - 1 - var lineCountDifference = 0 - - // Convert `""` to multi-line literal - if sourceCodeLines[functionLineIndex].hasSuffix(emptyStringLiteralWithCloseBrace) { - // Convert: - // _assertInlineSnapshot(matching: value, as: .dump, with: "") - // to: - // _assertInlineSnapshot(matching: value, as: .dump, with: """ - // """) - var functionCallLine = sourceCodeLines.remove(at: functionLineIndex) - functionCallLine.removeLast(emptyStringLiteralWithCloseBrace.count) - let indentText = indentation(of: functionCallLine) - sourceCodeLines.insert(contentsOf: [ - functionCallLine + multiLineStringLiteralTerminator, - indentText + multiLineStringLiteralTerminator + ")", - ] as [String.SubSequence], at: functionLineIndex) - lineCountDifference += 1 - } - - /// If they haven't got a multi-line literal by now, then just fail. - guard sourceCodeLines[functionLineIndex].hasSuffix(multiLineStringLiteralTerminator) else { - struct InlineError: LocalizedError { - var errorDescription: String? { - return """ -To use inline snapshots, please convert the "with" argument to a multi-line literal. -""" - } - } - throw InlineError() - } - - /// Find the end of multi-line literal and replace contents with recording. - if let multiLineLiteralEndIndex = sourceCodeLines[offsetStartIndex...].firstIndex(where: { $0.hasClosingMultilineStringDelimiter() }) { - - let diffableLines = context.diffable.split(separator: "\n") - - // Add #'s to the multiline string literal if needed - let numberSigns: String - if context.diffable.hasEscapedSpecialCharactersLiteral() { - numberSigns = String(repeating: "#", count: context.diffable.numberOfNumberSignsNeeded()) - } else if nil != diffableLines.first(where: { $0.endsInBackslash() }) { - // We want to avoid \ being interpreted as an escaped newline in the recorded inline snapshot - numberSigns = "#" - } else { - numberSigns = "" - } - let multiLineStringLiteralTerminatorPre = numberSigns + multiLineStringLiteralTerminator - let multiLineStringLiteralTerminatorPost = multiLineStringLiteralTerminator + numberSigns - - // Update opening (#...)""" - sourceCodeLines[functionLineIndex].replaceFirstOccurrence( - of: extendedOpeningStringDelimitersPattern, - with: multiLineStringLiteralTerminatorPre - ) - - // Update closing """(#...) - sourceCodeLines[multiLineLiteralEndIndex].replaceFirstOccurrence( - of: extendedClosingStringDelimitersPattern, - with: multiLineStringLiteralTerminatorPost - ) - - /// Convert actual value to Lines to insert - let indentText = indentation(of: sourceCodeLines[multiLineLiteralEndIndex]) - let newDiffableLines = context.diffable - .split(separator: "\n", omittingEmptySubsequences: false) - .map { Substring(indentText + $0) } - lineCountDifference += newDiffableLines.count - (multiLineLiteralEndIndex - offsetStartIndex) - - let fileRecording = FileRecording(line: context.lineIndex, difference: lineCountDifference) - - /// Insert the lines - sourceCodeLines.replaceSubrange(offsetStartIndex..(of str: S) -> String { - var count = 0 - for char in str { - guard char == " " else { break } - count += 1 - } - return String(repeating: " ", count: count) -} - -fileprivate extension Substring { - mutating func replaceFirstOccurrence(of pattern: String, with newString: String) { - let newString = replacingOccurrences(of: pattern, with: newString, options: .regularExpression) - self = Substring(newString) - } - - func hasOpeningMultilineStringDelimiter() -> Bool { - return range(of: extendedOpeningStringDelimitersPattern, options: .regularExpression) != nil - } - - func hasClosingMultilineStringDelimiter() -> Bool { - return range(of: extendedClosingStringDelimitersPattern, options: .regularExpression) != nil - } - - func endsInBackslash() -> Bool { - if let lastChar = last { - return lastChar == Character(#"\"#) - } - return false - } -} - -private let emptyStringLiteralWithCloseBrace = "\"\")" -private let multiLineStringLiteralTerminator = "\"\"\"" -private let extendedOpeningStringDelimitersPattern = #"#{0,}\"\"\""# -private let extendedClosingStringDelimitersPattern = ##"\"\"\"#{0,}"## - -// When we modify a file, the line numbers reported by the compiler through #line are no longer -// accurate. With the FileRecording values we keep track of we modify the files so we can adjust -// line numbers. -private var recordings: Recordings = [:] diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/AssertSnapshot.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/AssertSnapshot.swift deleted file mode 100644 index dd6ca4f..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/AssertSnapshot.swift +++ /dev/null @@ -1,313 +0,0 @@ -import XCTest - -/// Enhances failure messages with a command line diff tool expression that can be copied and pasted into a terminal. -/// -/// diffTool = "ksdiff" -public var diffTool: String? = nil - -/// Whether or not to record all new references. -public var isRecording = false - -/// Whether or not to record all new references. -/// Due to a name clash in Xcode 12, this has been renamed to `isRecording`. -@available(*, deprecated, renamed: "isRecording") -public var record: Bool { - get { isRecording } - set { isRecording = newValue } -} - -/// Asserts that a given value matches a reference on disk. -/// -/// - Parameters: -/// - value: A value to compare against a reference. -/// - snapshotting: A strategy for serializing, deserializing, and comparing values. -/// - name: An optional description of the snapshot. -/// - recording: Whether or not to record a new reference. -/// - timeout: The amount of time a snapshot must be generated in. -/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called. -/// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called. -/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called. -public func assertSnapshot( - matching value: @autoclosure () throws -> Value, - as snapshotting: Snapshotting, - named name: String? = nil, - record recording: Bool = false, - timeout: TimeInterval = 5, - file: StaticString = #file, - testName: String = #function, - line: UInt = #line - ) { - - let failure = verifySnapshot( - matching: try value(), - as: snapshotting, - named: name, - record: recording, - timeout: timeout, - file: file, - testName: testName, - line: line - ) - guard let message = failure else { return } - XCTFail(message, file: file, line: line) -} - -/// Asserts that a given value matches references on disk. -/// -/// - Parameters: -/// - value: A value to compare against a reference. -/// - snapshotting: A dictionary of names and strategies for serializing, deserializing, and comparing values. -/// - recording: Whether or not to record a new reference. -/// - timeout: The amount of time a snapshot must be generated in. -/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called. -/// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called. -/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called. -public func assertSnapshots( - matching value: @autoclosure () throws -> Value, - as strategies: [String: Snapshotting], - record recording: Bool = false, - timeout: TimeInterval = 5, - file: StaticString = #file, - testName: String = #function, - line: UInt = #line - ) { - - try? strategies.forEach { name, strategy in - assertSnapshot( - matching: try value(), - as: strategy, - named: name, - record: recording, - timeout: timeout, - file: file, - testName: testName, - line: line - ) - } -} - -/// Asserts that a given value matches references on disk. -/// -/// - Parameters: -/// - value: A value to compare against a reference. -/// - snapshotting: An array of strategies for serializing, deserializing, and comparing values. -/// - recording: Whether or not to record a new reference. -/// - timeout: The amount of time a snapshot must be generated in. -/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called. -/// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called. -/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called. -public func assertSnapshots( - matching value: @autoclosure () throws -> Value, - as strategies: [Snapshotting], - record recording: Bool = false, - timeout: TimeInterval = 5, - file: StaticString = #file, - testName: String = #function, - line: UInt = #line - ) { - - try? strategies.forEach { strategy in - assertSnapshot( - matching: try value(), - as: strategy, - record: recording, - timeout: timeout, - file: file, - testName: testName, - line: line - ) - } -} - -/// Verifies that a given value matches a reference on disk. -/// -/// Third party snapshot assert helpers can be built on top of this function. Simply invoke `verifySnapshot` with your own arguments, and then invoke `XCTFail` with the string returned if it is non-`nil`. For example, if you want the snapshot directory to be determined by an environment variable, you can create your own assert helper like so: -/// -/// public func myAssertSnapshot( -/// matching value: @autoclosure () throws -> Value, -/// as snapshotting: Snapshotting, -/// named name: String? = nil, -/// record recording: Bool = false, -/// timeout: TimeInterval = 5, -/// file: StaticString = #file, -/// testName: String = #function, -/// line: UInt = #line -/// ) { -/// -/// let snapshotDirectory = ProcessInfo.processInfo.environment["SNAPSHOT_REFERENCE_DIR"]! + "/" + #file -/// let failure = verifySnapshot( -/// matching: value, -/// as: snapshotting, -/// named: name, -/// record: recording, -/// snapshotDirectory: snapshotDirectory, -/// timeout: timeout, -/// file: file, -/// testName: testName -/// ) -/// guard let message = failure else { return } -/// XCTFail(message, file: file, line: line) -/// } -/// -/// - Parameters: -/// - value: A value to compare against a reference. -/// - snapshotting: A strategy for serializing, deserializing, and comparing values. -/// - name: An optional description of the snapshot. -/// - recording: Whether or not to record a new reference. -/// - snapshotDirectory: Optional directory to save snapshots. By default snapshots will be saved in a directory with the same name as the test file, and that directory will sit inside a directory `__Snapshots__` that sits next to your test file. -/// - timeout: The amount of time a snapshot must be generated in. -/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called. -/// - testName: The name of the test in which failure occurred. Defaults to the function name of the test case in which this function was called. -/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called. -/// - Returns: A failure message or, if the value matches, nil. -public func verifySnapshot( - matching value: @autoclosure () throws -> Value, - as snapshotting: Snapshotting, - named name: String? = nil, - record recording: Bool = false, - snapshotDirectory: String? = nil, - timeout: TimeInterval = 5, - file: StaticString = #file, - testName: String = #function, - line: UInt = #line - ) - -> String? { - - let recording = recording || isRecording - - do { - let fileUrl = URL(fileURLWithPath: "\(file)", isDirectory: false) - let fileName = fileUrl.deletingPathExtension().lastPathComponent - - let snapshotDirectoryUrl = snapshotDirectory.map { URL(fileURLWithPath: $0, isDirectory: true) } - ?? fileUrl - .deletingLastPathComponent() - .appendingPathComponent("__Snapshots__") - .appendingPathComponent(fileName) - - let identifier: String - if let name = name { - identifier = sanitizePathComponent(name) - } else { - let counter = counterQueue.sync { () -> Int in - let key = snapshotDirectoryUrl.appendingPathComponent(testName) - counterMap[key, default: 0] += 1 - return counterMap[key]! - } - identifier = String(counter) - } - - let testName = sanitizePathComponent(testName) - let snapshotFileUrl = snapshotDirectoryUrl - .appendingPathComponent("\(testName).\(identifier)") - .appendingPathExtension(snapshotting.pathExtension ?? "") - let fileManager = FileManager.default - try fileManager.createDirectory(at: snapshotDirectoryUrl, withIntermediateDirectories: true) - - let tookSnapshot = XCTestExpectation(description: "Took snapshot") - var optionalDiffable: Format? - snapshotting.snapshot(try value()).run { b in - optionalDiffable = b - tookSnapshot.fulfill() - } - let result = XCTWaiter.wait(for: [tookSnapshot], timeout: timeout) - switch result { - case .completed: - break - case .timedOut: - return """ - Exceeded timeout of \(timeout) seconds waiting for snapshot. - - This can happen when an asynchronously rendered view (like a web view) has not loaded. \ - Ensure that every subview of the view hierarchy has loaded to avoid timeouts, or, if a \ - timeout is unavoidable, consider setting the "timeout" parameter of "assertSnapshot" to \ - a higher value. - """ - case .incorrectOrder, .invertedFulfillment, .interrupted: - return "Couldn't snapshot value" - @unknown default: - return "Couldn't snapshot value" - } - - guard var diffable = optionalDiffable else { - return "Couldn't snapshot value" - } - - guard !recording, fileManager.fileExists(atPath: snapshotFileUrl.path) else { - try snapshotting.diffing.toData(diffable).write(to: snapshotFileUrl) - return recording - ? """ - Record mode is on. Turn record mode off and re-run "\(testName)" to test against the newly-recorded snapshot. - - open "\(snapshotFileUrl.path)" - - Recorded snapshot: 
 - """ - : """ - No reference was found on disk. Automatically recorded snapshot: 
 - - open "\(snapshotFileUrl.path)" - - Re-run "\(testName)" to test against the newly-recorded snapshot. - """ - } - - let data = try Data(contentsOf: snapshotFileUrl) - let reference = snapshotting.diffing.fromData(data) - - #if os(iOS) || os(tvOS) - // If the image generation fails for the diffable part use the reference - if let localDiff = diffable as? UIImage, localDiff.size == .zero { - diffable = reference - } - #endif - - guard let (failure, attachments) = snapshotting.diffing.diff(reference, diffable) else { - return nil - } - - let artifactsUrl = URL( - fileURLWithPath: ProcessInfo.processInfo.environment["SNAPSHOT_ARTIFACTS"] ?? NSTemporaryDirectory(), isDirectory: true - ) - let artifactsSubUrl = artifactsUrl.appendingPathComponent(fileName) - try fileManager.createDirectory(at: artifactsSubUrl, withIntermediateDirectories: true) - let failedSnapshotFileUrl = artifactsSubUrl.appendingPathComponent(snapshotFileUrl.lastPathComponent) - try snapshotting.diffing.toData(diffable).write(to: failedSnapshotFileUrl) - - if !attachments.isEmpty { - #if !os(Linux) - if ProcessInfo.processInfo.environment.keys.contains("__XCODE_BUILT_PRODUCTS_DIR_PATHS") { - XCTContext.runActivity(named: "Attached Failure Diff") { activity in - attachments.forEach { - activity.add($0) - } - } - } - #endif - } - - let diffMessage = diffTool - .map { "\($0) \"\(snapshotFileUrl.path)\" \"\(failedSnapshotFileUrl.path)\"" } - ?? "@\(minus)\n\"\(snapshotFileUrl.path)\"\n@\(plus)\n\"\(failedSnapshotFileUrl.path)\"" - return """ - Snapshot does not match reference. - - \(diffMessage) - - \(failure.trimmingCharacters(in: .whitespacesAndNewlines)) - """ - } catch { - return error.localizedDescription - } -} - -// MARK: - Private - -private let counterQueue = DispatchQueue(label: "co.pointfree.SnapshotTesting.counter") -private var counterMap: [URL: Int] = [:] - -func sanitizePathComponent(_ string: String) -> String { - return string - .replacingOccurrences(of: "\\W+", with: "-", options: .regularExpression) - .replacingOccurrences(of: "^-|-$", with: "", options: .regularExpression) -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Async.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Async.swift deleted file mode 100644 index 427d5a5..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Async.swift +++ /dev/null @@ -1,39 +0,0 @@ -/// A wrapper around an asynchronous operation. -/// -/// Snapshot strategies may utilize this type to create snapshots in an asynchronous fashion. -/// -/// 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. -/// -/// Async { callback in -/// webView.takeSnapshot(with: nil) { image, error in -/// callback(image!) -/// } -/// } -public struct Async { - public let run: (@escaping (Value) -> Void) -> Void - - /// Creates an asynchronous operation. - /// - /// - Parameters: - /// - run: A function that, when called, can hand a value to a callback. - /// - callback: A function that can be called with a value. - public init(run: @escaping (_ callback: @escaping (Value) -> Void) -> Void) { - self.run = run - } - - /// Wraps a pure value in an asynchronous operation. - /// - /// - Parameter value: A value to be wrapped in an asynchronous operation. - public init(value: Value) { - self.init { callback in callback(value) } - } - - /// Transforms an Async into an Async with a function `(Value) -> NewValue`. - /// - /// - Parameter f: A transformation to apply to the value wrapped by the async value. - public func map(_ f: @escaping (Value) -> NewValue) -> Async { - return .init { callback in - self.run { a in callback(f(a)) } - } - } -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/Internal.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/Internal.swift deleted file mode 100644 index af77913..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/Internal.swift +++ /dev/null @@ -1,11 +0,0 @@ -#if os(macOS) -import Cocoa -typealias Image = NSImage -typealias ImageView = NSImageView -typealias View = NSView -#elseif os(iOS) || os(tvOS) -import UIKit -typealias Image = UIImage -typealias ImageView = UIImageView -typealias View = UIView -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/PlistEncoder.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/PlistEncoder.swift deleted file mode 100644 index 589557e..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/PlistEncoder.swift +++ /dev/null @@ -1,1828 +0,0 @@ -// NB: This file is copied from Swift to make available to Linux Swift 4.2 and below. -// -// https://github.com/apple/swift/blob/master/stdlib/public/SDK/Foundation/PlistEncoder.swift -import Foundation - -extension DecodingError { - internal static func _typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: Any) -> DecodingError { - let description = "Expected to decode \(expectation) but found \(type(of: reality)) instead." - return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description)) - } -} - -let kCFBooleanTrue = NSNumber(booleanLiteral: true) -let kCFBooleanFalse = NSNumber(booleanLiteral: false) - - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -//===----------------------------------------------------------------------===// -// Plist Encoder -//===----------------------------------------------------------------------===// - -/// `PropertyListEncoder` facilitates the encoding of `Encodable` values into property lists. -open class PropertyListEncoder { - - // MARK: - Options - - /// The output format to write the property list data in. Defaults to `.binary`. - open var outputFormat: PropertyListSerialization.PropertyListFormat = .binary - - /// Contextual user-provided information for use during encoding. - open var userInfo: [CodingUserInfoKey : Any] = [:] - - /// Options set on the top-level encoder to pass down the encoding hierarchy. - fileprivate struct _Options { - let outputFormat: PropertyListSerialization.PropertyListFormat - let userInfo: [CodingUserInfoKey : Any] - } - - /// The options set on the top-level encoder. - fileprivate var options: _Options { - return _Options(outputFormat: outputFormat, userInfo: userInfo) - } - - // MARK: - Constructing a Property List Encoder - - /// Initializes `self` with default strategies. - public init() {} - - // MARK: - Encoding Values - - /// Encodes the given top-level value and returns its property list representation. - /// - /// - parameter value: The value to encode. - /// - returns: A new `Data` value containing the encoded property list data. - /// - throws: `EncodingError.invalidValue` if a non-conforming floating-point value is encountered during encoding, and the encoding strategy is `.throw`. - /// - throws: An error if any value throws an error during encoding. - open func encode(_ value: Value) throws -> Data { - let topLevel = try encodeToTopLevelContainer(value) - if topLevel is NSNumber { - throw EncodingError.invalidValue(value, - EncodingError.Context(codingPath: [], - debugDescription: "Top-level \(Value.self) encoded as number property list fragment.")) - } else if topLevel is NSString { - throw EncodingError.invalidValue(value, - EncodingError.Context(codingPath: [], - debugDescription: "Top-level \(Value.self) encoded as string property list fragment.")) - } else if topLevel is NSDate { - throw EncodingError.invalidValue(value, - EncodingError.Context(codingPath: [], - debugDescription: "Top-level \(Value.self) encoded as date property list fragment.")) - } - - do { - return try PropertyListSerialization.data(fromPropertyList: topLevel, format: self.outputFormat, options: 0) - } catch { - throw EncodingError.invalidValue(value, - EncodingError.Context(codingPath: [], debugDescription: "Unable to encode the given top-level value as a property list", underlyingError: error)) - } - } - - /// Encodes the given top-level value and returns its plist-type representation. - /// - /// - parameter value: The value to encode. - /// - returns: A new top-level array or dictionary representing the value. - /// - throws: `EncodingError.invalidValue` if a non-conforming floating-point value is encountered during encoding, and the encoding strategy is `.throw`. - /// - throws: An error if any value throws an error during encoding. - internal func encodeToTopLevelContainer(_ value: Value) throws -> Any { - let encoder = _PlistEncoder(options: self.options) - guard let topLevel = try encoder.box_(value) else { - throw EncodingError.invalidValue(value, - EncodingError.Context(codingPath: [], - debugDescription: "Top-level \(Value.self) did not encode any values.")) - } - - return topLevel - } -} - -// MARK: - _PlistEncoder - -fileprivate class _PlistEncoder : Encoder { - // MARK: Properties - - /// The encoder's storage. - fileprivate var storage: _PlistEncodingStorage - - /// Options set on the top-level encoder. - fileprivate let options: PropertyListEncoder._Options - - /// The path to the current point in encoding. - fileprivate(set) public var codingPath: [CodingKey] - - /// Contextual user-provided information for use during encoding. - public var userInfo: [CodingUserInfoKey : Any] { - return self.options.userInfo - } - - // MARK: - Initialization - - /// Initializes `self` with the given top-level encoder options. - fileprivate init(options: PropertyListEncoder._Options, codingPath: [CodingKey] = []) { - self.options = options - self.storage = _PlistEncodingStorage() - self.codingPath = codingPath - } - - /// Returns whether a new element can be encoded at this coding path. - /// - /// `true` if an element has not yet been encoded at this coding path; `false` otherwise. - fileprivate var canEncodeNewValue: Bool { - // Every time a new value gets encoded, the key it's encoded for is pushed onto the coding path (even if it's a nil key from an unkeyed container). - // At the same time, every time a container is requested, a new value gets pushed onto the storage stack. - // If there are more values on the storage stack than on the coding path, it means the value is requesting more than one container, which violates the precondition. - // - // This means that anytime something that can request a new container goes onto the stack, we MUST push a key onto the coding path. - // Things which will not request containers do not need to have the coding path extended for them (but it doesn't matter if it is, because they will not reach here). - return self.storage.count == self.codingPath.count - } - - // MARK: - Encoder Methods - public func container(keyedBy: Key.Type) -> KeyedEncodingContainer { - // If an existing keyed container was already requested, return that one. - let topContainer: NSMutableDictionary - if self.canEncodeNewValue { - // We haven't yet pushed a container at this level; do so here. - topContainer = self.storage.pushKeyedContainer() - } else { - guard let container = self.storage.containers.last as? NSMutableDictionary else { - preconditionFailure("Attempt to push new keyed encoding container when already previously encoded at this path.") - } - - topContainer = container - } - - let container = _PlistKeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) - return KeyedEncodingContainer(container) - } - - public func unkeyedContainer() -> UnkeyedEncodingContainer { - // If an existing unkeyed container was already requested, return that one. - let topContainer: NSMutableArray - if self.canEncodeNewValue { - // We haven't yet pushed a container at this level; do so here. - topContainer = self.storage.pushUnkeyedContainer() - } else { - guard let container = self.storage.containers.last as? NSMutableArray else { - preconditionFailure("Attempt to push new unkeyed encoding container when already previously encoded at this path.") - } - - topContainer = container - } - - return _PlistUnkeyedEncodingContainer(referencing: self, codingPath: self.codingPath, wrapping: topContainer) - } - - public func singleValueContainer() -> SingleValueEncodingContainer { - return self - } -} - -// MARK: - Encoding Storage and Containers - -fileprivate struct _PlistEncodingStorage { - // MARK: Properties - - /// The container stack. - /// Elements may be any one of the plist types (NSNumber, NSString, NSDate, NSArray, NSDictionary). - private(set) fileprivate var containers: [NSObject] = [] - - // MARK: - Initialization - - /// Initializes `self` with no containers. - fileprivate init() {} - - // MARK: - Modifying the Stack - - fileprivate var count: Int { - return self.containers.count - } - - fileprivate mutating func pushKeyedContainer() -> NSMutableDictionary { - let dictionary = NSMutableDictionary() - self.containers.append(dictionary) - return dictionary - } - - fileprivate mutating func pushUnkeyedContainer() -> NSMutableArray { - let array = NSMutableArray() - self.containers.append(array) - return array - } - - fileprivate mutating func push(container: __owned NSObject) { - self.containers.append(container) - } - - fileprivate mutating func popContainer() -> NSObject { - precondition(!self.containers.isEmpty, "Empty container stack.") - return self.containers.popLast()! - } -} - -// MARK: - Encoding Containers - -fileprivate struct _PlistKeyedEncodingContainer : KeyedEncodingContainerProtocol { - typealias Key = K - - // MARK: Properties - - /// A reference to the encoder we're writing to. - private let encoder: _PlistEncoder - - /// A reference to the container we're writing to. - private let container: NSMutableDictionary - - /// The path of coding keys taken to get to this point in encoding. - private(set) public var codingPath: [CodingKey] - - // MARK: - Initialization - - /// Initializes `self` with the given references. - fileprivate init(referencing encoder: _PlistEncoder, codingPath: [CodingKey], wrapping container: NSMutableDictionary) { - self.encoder = encoder - self.codingPath = codingPath - self.container = container - } - - // MARK: - KeyedEncodingContainerProtocol Methods - - public mutating func encodeNil(forKey key: Key) throws { self.container[key.stringValue] = _plistNullNSString } - public mutating func encode(_ value: Bool, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: Int, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: Int8, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: Int16, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: Int32, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: Int64, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: UInt, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: UInt8, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: UInt16, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: UInt32, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: UInt64, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: String, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: Float, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - public mutating func encode(_ value: Double, forKey key: Key) throws { self.container[key.stringValue] = self.encoder.box(value) } - - public mutating func encode(_ value: T, forKey key: Key) throws { - self.encoder.codingPath.append(key) - defer { self.encoder.codingPath.removeLast() } - self.container[key.stringValue] = try self.encoder.box(value) - } - - public mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { - let dictionary = NSMutableDictionary() - self.container[key.stringValue] = dictionary - - self.codingPath.append(key) - defer { self.codingPath.removeLast() } - - let container = _PlistKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) - return KeyedEncodingContainer(container) - } - - public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { - let array = NSMutableArray() - self.container[key.stringValue] = array - - self.codingPath.append(key) - defer { self.codingPath.removeLast() } - return _PlistUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) - } - - public mutating func superEncoder() -> Encoder { - return _PlistReferencingEncoder(referencing: self.encoder, at: _PlistKey.super, wrapping: self.container) - } - - public mutating func superEncoder(forKey key: Key) -> Encoder { - return _PlistReferencingEncoder(referencing: self.encoder, at: key, wrapping: self.container) - } -} - -fileprivate struct _PlistUnkeyedEncodingContainer : UnkeyedEncodingContainer { - // MARK: Properties - - /// A reference to the encoder we're writing to. - private let encoder: _PlistEncoder - - /// A reference to the container we're writing to. - private let container: NSMutableArray - - /// The path of coding keys taken to get to this point in encoding. - private(set) public var codingPath: [CodingKey] - - /// The number of elements encoded into the container. - public var count: Int { - return self.container.count - } - - // MARK: - Initialization - - /// Initializes `self` with the given references. - fileprivate init(referencing encoder: _PlistEncoder, codingPath: [CodingKey], wrapping container: NSMutableArray) { - self.encoder = encoder - self.codingPath = codingPath - self.container = container - } - - // MARK: - UnkeyedEncodingContainer Methods - - public mutating func encodeNil() throws { self.container.add(_plistNullNSString) } - public mutating func encode(_ value: Bool) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int8) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int16) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int32) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Int64) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt8) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt16) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt32) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: UInt64) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Float) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: Double) throws { self.container.add(self.encoder.box(value)) } - public mutating func encode(_ value: String) throws { self.container.add(self.encoder.box(value)) } - - public mutating func encode(_ value: T) throws { - self.encoder.codingPath.append(_PlistKey(index: self.count)) - defer { self.encoder.codingPath.removeLast() } - self.container.add(try self.encoder.box(value)) - } - - public mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer { - self.codingPath.append(_PlistKey(index: self.count)) - defer { self.codingPath.removeLast() } - - let dictionary = NSMutableDictionary() - self.container.add(dictionary) - - let container = _PlistKeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: dictionary) - return KeyedEncodingContainer(container) - } - - public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { - self.codingPath.append(_PlistKey(index: self.count)) - defer { self.codingPath.removeLast() } - - let array = NSMutableArray() - self.container.add(array) - return _PlistUnkeyedEncodingContainer(referencing: self.encoder, codingPath: self.codingPath, wrapping: array) - } - - public mutating func superEncoder() -> Encoder { - return _PlistReferencingEncoder(referencing: self.encoder, at: self.container.count, wrapping: self.container) - } -} - -extension _PlistEncoder : SingleValueEncodingContainer { - // MARK: - SingleValueEncodingContainer Methods - - private func assertCanEncodeNewValue() { - precondition(self.canEncodeNewValue, "Attempt to encode value through single value container when previously value already encoded.") - } - - public func encodeNil() throws { - assertCanEncodeNewValue() - self.storage.push(container: _plistNullNSString) - } - - public func encode(_ value: Bool) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: Int) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: Int8) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: Int16) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: Int32) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: Int64) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: UInt) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: UInt8) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: UInt16) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: UInt32) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: UInt64) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: String) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: Float) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: Double) throws { - assertCanEncodeNewValue() - self.storage.push(container: self.box(value)) - } - - public func encode(_ value: T) throws { - assertCanEncodeNewValue() - try self.storage.push(container: self.box(value)) - } -} - -// MARK: - Concrete Value Representations - -extension _PlistEncoder { - - /// Returns the given value boxed in a container appropriate for pushing onto the container stack. - fileprivate func box(_ value: Bool) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int8) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int16) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int32) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Int64) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Float) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: Double) -> NSObject { return NSNumber(value: value) } - fileprivate func box(_ value: String) -> NSObject { return NSString(string: value) } - - fileprivate func box(_ value: T) throws -> NSObject { - return try self.box_(value) ?? NSDictionary() - } - - fileprivate func box_(_ value: T) throws -> NSObject? { - if T.self == Date.self || T.self == NSDate.self { - // PropertyListSerialization handles NSDate directly. - return (value as! NSDate) - } else if T.self == Data.self || T.self == NSData.self { - // PropertyListSerialization handles NSData directly. - return (value as! NSData) - } - - // The value should request a container from the _PlistEncoder. - let depth = self.storage.count - do { - try value.encode(to: self) - } catch let error { - // If the value pushed a container before throwing, pop it back off to restore state. - if self.storage.count > depth { - let _ = self.storage.popContainer() - } - - throw error - } - - // The top container should be a new container. - guard self.storage.count > depth else { - return nil - } - - return self.storage.popContainer() - } -} - -// MARK: - _PlistReferencingEncoder - -/// _PlistReferencingEncoder is a special subclass of _PlistEncoder which has its own storage, but references the contents of a different encoder. -/// It's used in superEncoder(), which returns a new encoder for encoding a superclass -- the lifetime of the encoder should not escape the scope it's created in, but it doesn't necessarily know when it's done being used (to write to the original container). -fileprivate class _PlistReferencingEncoder : _PlistEncoder { - // MARK: Reference types. - - /// The type of container we're referencing. - private enum Reference { - /// Referencing a specific index in an array container. - case array(NSMutableArray, Int) - - /// Referencing a specific key in a dictionary container. - case dictionary(NSMutableDictionary, String) - } - - // MARK: - Properties - - /// The encoder we're referencing. - private let encoder: _PlistEncoder - - /// The container reference itself. - private let reference: Reference - - // MARK: - Initialization - - /// Initializes `self` by referencing the given array container in the given encoder. - fileprivate init(referencing encoder: _PlistEncoder, at index: Int, wrapping array: NSMutableArray) { - self.encoder = encoder - self.reference = .array(array, index) - super.init(options: encoder.options, codingPath: encoder.codingPath) - - self.codingPath.append(_PlistKey(index: index)) - } - - /// Initializes `self` by referencing the given dictionary container in the given encoder. - fileprivate init(referencing encoder: _PlistEncoder, at key: CodingKey, wrapping dictionary: NSMutableDictionary) { - self.encoder = encoder - self.reference = .dictionary(dictionary, key.stringValue) - super.init(options: encoder.options, codingPath: encoder.codingPath) - - self.codingPath.append(key) - } - - // MARK: - Coding Path Operations - - fileprivate override var canEncodeNewValue: Bool { - // With a regular encoder, the storage and coding path grow together. - // A referencing encoder, however, inherits its parents coding path, as well as the key it was created for. - // We have to take this into account. - return self.storage.count == self.codingPath.count - self.encoder.codingPath.count - 1 - } - - // MARK: - Deinitialization - - // Finalizes `self` by writing the contents of our storage to the referenced encoder's storage. - deinit { - let value: Any - switch self.storage.count { - case 0: value = NSDictionary() - case 1: value = self.storage.popContainer() - default: fatalError("Referencing encoder deallocated with multiple containers on stack.") - } - - switch self.reference { - case .array(let array, let index): - array.insert(value, at: index) - - case .dictionary(let dictionary, let key): - dictionary[NSString(string: key)] = value - } - } -} - -//===----------------------------------------------------------------------===// -// Plist Decoder -//===----------------------------------------------------------------------===// - -/// `PropertyListDecoder` facilitates the decoding of property list values into semantic `Decodable` types. -open class PropertyListDecoder { - // MARK: Options - - /// Contextual user-provided information for use during decoding. - open var userInfo: [CodingUserInfoKey : Any] = [:] - - /// Options set on the top-level encoder to pass down the decoding hierarchy. - fileprivate struct _Options { - let userInfo: [CodingUserInfoKey : Any] - } - - /// The options set on the top-level decoder. - fileprivate var options: _Options { - return _Options(userInfo: userInfo) - } - - // MARK: - Constructing a Property List Decoder - - /// Initializes `self` with default strategies. - public init() {} - - // MARK: - Decoding Values - - /// Decodes a top-level value of the given type from the given property list representation. - /// - /// - parameter type: The type of the value to decode. - /// - parameter data: The data to decode from. - /// - returns: A value of the requested type. - /// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not a valid property list. - /// - throws: An error if any value throws an error during decoding. - open func decode(_ type: T.Type, from data: Data) throws -> T { - var format: PropertyListSerialization.PropertyListFormat = .binary - return try decode(type, from: data, format: &format) - } - - /// Decodes a top-level value of the given type from the given property list representation. - /// - /// - parameter type: The type of the value to decode. - /// - parameter data: The data to decode from. - /// - parameter format: The parsed property list format. - /// - returns: A value of the requested type along with the detected format of the property list. - /// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not a valid property list. - /// - throws: An error if any value throws an error during decoding. - open func decode(_ type: T.Type, from data: Data, format: inout PropertyListSerialization.PropertyListFormat) throws -> T { - let topLevel: Any - do { - topLevel = try PropertyListSerialization.propertyList(from: data, options: [], format: &format) - } catch { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not a valid property list.", underlyingError: error)) - } - - return try decode(type, fromTopLevel: topLevel) - } - - /// Decodes a top-level value of the given type from the given property list container (top-level array or dictionary). - /// - /// - parameter type: The type of the value to decode. - /// - parameter container: The top-level plist container. - /// - returns: A value of the requested type. - /// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not a valid property list. - /// - throws: An error if any value throws an error during decoding. - internal func decode(_ type: T.Type, fromTopLevel container: Any) throws -> T { - let decoder = _PlistDecoder(referencing: container, options: self.options) - guard let value = try decoder.unbox(container, as: type) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value.")) - } - - return value - } -} - -// MARK: - _PlistDecoder - -fileprivate class _PlistDecoder : Decoder { - // MARK: Properties - - /// The decoder's storage. - fileprivate var storage: _PlistDecodingStorage - - /// Options set on the top-level decoder. - fileprivate let options: PropertyListDecoder._Options - - /// The path to the current point in encoding. - fileprivate(set) public var codingPath: [CodingKey] - - /// Contextual user-provided information for use during encoding. - public var userInfo: [CodingUserInfoKey : Any] { - return self.options.userInfo - } - - // MARK: - Initialization - - /// Initializes `self` with the given top-level container and options. - fileprivate init(referencing container: Any, at codingPath: [CodingKey] = [], options: PropertyListDecoder._Options) { - self.storage = _PlistDecodingStorage() - self.storage.push(container: container) - self.codingPath = codingPath - self.options = options - } - - // MARK: - Decoder Methods - - public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { - guard !(self.storage.topContainer is NSNull) else { - throw DecodingError.valueNotFound(KeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get keyed decoding container -- found null value instead.")) - } - - guard let topContainer = self.storage.topContainer as? [String : Any] else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: self.storage.topContainer) - } - - let container = _PlistKeyedDecodingContainer(referencing: self, wrapping: topContainer) - return KeyedDecodingContainer(container) - } - - public func unkeyedContainer() throws -> UnkeyedDecodingContainer { - guard !(self.storage.topContainer is NSNull) else { - throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get unkeyed decoding container -- found null value instead.")) - } - - guard let topContainer = self.storage.topContainer as? [Any] else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: self.storage.topContainer) - } - - return _PlistUnkeyedDecodingContainer(referencing: self, wrapping: topContainer) - } - - public func singleValueContainer() throws -> SingleValueDecodingContainer { - return self - } -} - -// MARK: - Decoding Storage - -fileprivate struct _PlistDecodingStorage { - // MARK: Properties - - /// The container stack. - /// Elements may be any one of the plist types (NSNumber, Date, String, Array, [String : Any]). - private(set) fileprivate var containers: [Any] = [] - - // MARK: - Initialization - - /// Initializes `self` with no containers. - fileprivate init() {} - - // MARK: - Modifying the Stack - - fileprivate var count: Int { - return self.containers.count - } - - fileprivate var topContainer: Any { - precondition(!self.containers.isEmpty, "Empty container stack.") - return self.containers.last! - } - - fileprivate mutating func push(container: __owned Any) { - self.containers.append(container) - } - - fileprivate mutating func popContainer() { - precondition(!self.containers.isEmpty, "Empty container stack.") - self.containers.removeLast() - } -} - -// MARK: Decoding Containers - -fileprivate struct _PlistKeyedDecodingContainer : KeyedDecodingContainerProtocol { - typealias Key = K - - // MARK: Properties - - /// A reference to the decoder we're reading from. - private let decoder: _PlistDecoder - - /// A reference to the container we're reading from. - private let container: [String : Any] - - /// The path of coding keys taken to get to this point in decoding. - private(set) public var codingPath: [CodingKey] - - // MARK: - Initialization - - /// Initializes `self` by referencing the given decoder and container. - fileprivate init(referencing decoder: _PlistDecoder, wrapping container: [String : Any]) { - self.decoder = decoder - self.container = container - self.codingPath = decoder.codingPath - } - - // MARK: - KeyedDecodingContainerProtocol Methods - - public var allKeys: [Key] { - return self.container.keys.compactMap { Key(stringValue: $0) } - } - - public func contains(_ key: Key) -> Bool { - return self.container[key.stringValue] != nil - } - - public func decodeNil(forKey key: Key) throws -> Bool { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - guard let value = entry as? String else { - return false - } - - return value == _plistNull - } - - public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Bool.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Int.Type, forKey key: Key) throws -> Int { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Int.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Int8.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Int16.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Int32.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Int64.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: UInt.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: UInt8.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: UInt16.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: UInt32.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: UInt64.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Float.Type, forKey key: Key) throws -> Float { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - guard let value = try self.decoder.unbox(entry, as: Float.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: Double.Type, forKey key: Key) throws -> Double { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: Double.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: String.Type, forKey key: Key) throws -> String { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: String.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func decode(_ type: T.Type, forKey key: Key) throws -> T { - guard let entry = self.container[key.stringValue] else { - throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").")) - } - - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = try self.decoder.unbox(entry, as: type) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead.")) - } - - return value - } - - public func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer { - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = self.container[key.stringValue] else { - throw DecodingError.valueNotFound(KeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get nested keyed container -- no value found for key \"\(key.stringValue)\"")) - } - - guard let dictionary = value as? [String : Any] else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: value) - } - - let container = _PlistKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) - return KeyedDecodingContainer(container) - } - - public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - guard let value = self.container[key.stringValue] else { - throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get nested unkeyed container -- no value found for key \"\(key.stringValue)\"")) - } - - guard let array = value as? [Any] else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: value) - } - - return _PlistUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) - } - - private func _superDecoder(forKey key: __owned CodingKey) throws -> Decoder { - self.decoder.codingPath.append(key) - defer { self.decoder.codingPath.removeLast() } - - let value: Any = self.container[key.stringValue] ?? NSNull() - return _PlistDecoder(referencing: value, at: self.decoder.codingPath, options: self.decoder.options) - } - - public func superDecoder() throws -> Decoder { - return try _superDecoder(forKey: _PlistKey.super) - } - - public func superDecoder(forKey key: Key) throws -> Decoder { - return try _superDecoder(forKey: key) - } -} - -fileprivate struct _PlistUnkeyedDecodingContainer : UnkeyedDecodingContainer { - // MARK: Properties - - /// A reference to the decoder we're reading from. - private let decoder: _PlistDecoder - - /// A reference to the container we're reading from. - private let container: [Any] - - /// The path of coding keys taken to get to this point in decoding. - private(set) public var codingPath: [CodingKey] - - /// The index of the element we're about to decode. - private(set) public var currentIndex: Int - - // MARK: - Initialization - - /// Initializes `self` by referencing the given decoder and container. - fileprivate init(referencing decoder: _PlistDecoder, wrapping container: [Any]) { - self.decoder = decoder - self.container = container - self.codingPath = decoder.codingPath - self.currentIndex = 0 - } - - // MARK: - UnkeyedDecodingContainer Methods - - public var count: Int? { - return self.container.count - } - - public var isAtEnd: Bool { - return self.currentIndex >= self.count! - } - - public mutating func decodeNil() throws -> Bool { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(Any?.self, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - if self.container[self.currentIndex] is NSNull { - self.currentIndex += 1 - return true - } else { - return false - } - } - - public mutating func decode(_ type: Bool.Type) throws -> Bool { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Bool.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Int.Type) throws -> Int { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Int8.Type) throws -> Int8 { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int8.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Int16.Type) throws -> Int16 { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int16.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Int32.Type) throws -> Int32 { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int32.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Int64.Type) throws -> Int64 { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int64.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: UInt.Type) throws -> UInt { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: UInt8.Type) throws -> UInt8 { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt8.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: UInt16.Type) throws -> UInt16 { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt16.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: UInt32.Type) throws -> UInt32 { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt32.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: UInt64.Type) throws -> UInt64 { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt64.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Float.Type) throws -> Float { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Float.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: Double.Type) throws -> Double { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Double.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: String.Type) throws -> String { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: String.self) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func decode(_ type: T.Type) throws -> T { - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end.")) - } - - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: type) else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_PlistKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead.")) - } - - self.currentIndex += 1 - return decoded - } - - public mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer { - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(KeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get nested keyed container -- unkeyed container is at end.")) - } - - let value = self.container[self.currentIndex] - guard !(value is NSNull) else { - throw DecodingError.valueNotFound(KeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get keyed decoding container -- found null value instead.")) - } - - guard let dictionary = value as? [String : Any] else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: value) - } - - self.currentIndex += 1 - let container = _PlistKeyedDecodingContainer(referencing: self.decoder, wrapping: dictionary) - return KeyedDecodingContainer(container) - } - - public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get nested unkeyed container -- unkeyed container is at end.")) - } - - let value = self.container[self.currentIndex] - guard !(value is NSNull) else { - throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, - DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get keyed decoding container -- found null value instead.")) - } - - guard let array = value as? [Any] else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: value) - } - - self.currentIndex += 1 - return _PlistUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array) - } - - public mutating func superDecoder() throws -> Decoder { - self.decoder.codingPath.append(_PlistKey(index: self.currentIndex)) - defer { self.decoder.codingPath.removeLast() } - - guard !self.isAtEnd else { - throw DecodingError.valueNotFound(Decoder.self, DecodingError.Context(codingPath: self.codingPath, - debugDescription: "Cannot get superDecoder() -- unkeyed container is at end.")) - } - - let value = self.container[self.currentIndex] - self.currentIndex += 1 - return _PlistDecoder(referencing: value, at: self.decoder.codingPath, options: self.decoder.options) - } -} - -extension _PlistDecoder : SingleValueDecodingContainer { - // MARK: SingleValueDecodingContainer Methods - - private func expectNonNull(_ type: T.Type) throws { - guard !self.decodeNil() else { - throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "Expected \(type) but found null value instead.")) - } - } - - public func decodeNil() -> Bool { - guard let string = self.storage.topContainer as? String else { - return false - } - - return string == _plistNull - } - - public func decode(_ type: Bool.Type) throws -> Bool { - try expectNonNull(Bool.self) - return try self.unbox(self.storage.topContainer, as: Bool.self)! - } - - public func decode(_ type: Int.Type) throws -> Int { - try expectNonNull(Int.self) - return try self.unbox(self.storage.topContainer, as: Int.self)! - } - - public func decode(_ type: Int8.Type) throws -> Int8 { - try expectNonNull(Int8.self) - return try self.unbox(self.storage.topContainer, as: Int8.self)! - } - - public func decode(_ type: Int16.Type) throws -> Int16 { - try expectNonNull(Int16.self) - return try self.unbox(self.storage.topContainer, as: Int16.self)! - } - - public func decode(_ type: Int32.Type) throws -> Int32 { - try expectNonNull(Int32.self) - return try self.unbox(self.storage.topContainer, as: Int32.self)! - } - - public func decode(_ type: Int64.Type) throws -> Int64 { - try expectNonNull(Int64.self) - return try self.unbox(self.storage.topContainer, as: Int64.self)! - } - - public func decode(_ type: UInt.Type) throws -> UInt { - try expectNonNull(UInt.self) - return try self.unbox(self.storage.topContainer, as: UInt.self)! - } - - public func decode(_ type: UInt8.Type) throws -> UInt8 { - try expectNonNull(UInt8.self) - return try self.unbox(self.storage.topContainer, as: UInt8.self)! - } - - public func decode(_ type: UInt16.Type) throws -> UInt16 { - try expectNonNull(UInt16.self) - return try self.unbox(self.storage.topContainer, as: UInt16.self)! - } - - public func decode(_ type: UInt32.Type) throws -> UInt32 { - try expectNonNull(UInt32.self) - return try self.unbox(self.storage.topContainer, as: UInt32.self)! - } - - public func decode(_ type: UInt64.Type) throws -> UInt64 { - try expectNonNull(UInt64.self) - return try self.unbox(self.storage.topContainer, as: UInt64.self)! - } - - public func decode(_ type: Float.Type) throws -> Float { - try expectNonNull(Float.self) - return try self.unbox(self.storage.topContainer, as: Float.self)! - } - - public func decode(_ type: Double.Type) throws -> Double { - try expectNonNull(Double.self) - return try self.unbox(self.storage.topContainer, as: Double.self)! - } - - public func decode(_ type: String.Type) throws -> String { - try expectNonNull(String.self) - return try self.unbox(self.storage.topContainer, as: String.self)! - } - - public func decode(_ type: T.Type) throws -> T { - try expectNonNull(type) - return try self.unbox(self.storage.topContainer, as: type)! - } -} - -// MARK: - Concrete Value Representations - -extension _PlistDecoder { - /// Returns the given value unboxed from a container. - fileprivate func unbox(_ value: Any, as type: Bool.Type) throws -> Bool? { - if let string = value as? String, string == _plistNull { return nil } - - if let number = value as? NSNumber { - // TODO: Add a flag to coerce non-boolean numbers into Bools? - if number === kCFBooleanTrue as NSNumber { - return true - } else if number === kCFBooleanFalse as NSNumber { - return false - } - - /* FIXME: If swift-corelibs-foundation doesn't change to use NSNumber, this code path will need to be included and tested: - } else if let bool = value as? Bool { - return bool - */ - - } - - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - fileprivate func unbox(_ value: Any, as type: Int.Type) throws -> Int? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let int = number.intValue - guard NSNumber(value: int) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return int - } - - fileprivate func unbox(_ value: Any, as type: Int8.Type) throws -> Int8? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let int8 = number.int8Value - guard NSNumber(value: int8) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return int8 - } - - fileprivate func unbox(_ value: Any, as type: Int16.Type) throws -> Int16? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let int16 = number.int16Value - guard NSNumber(value: int16) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return int16 - } - - fileprivate func unbox(_ value: Any, as type: Int32.Type) throws -> Int32? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let int32 = number.int32Value - guard NSNumber(value: int32) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return int32 - } - - fileprivate func unbox(_ value: Any, as type: Int64.Type) throws -> Int64? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let int64 = number.int64Value - guard NSNumber(value: int64) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return int64 - } - - fileprivate func unbox(_ value: Any, as type: UInt.Type) throws -> UInt? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let uint = number.uintValue - guard NSNumber(value: uint) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return uint - } - - fileprivate func unbox(_ value: Any, as type: UInt8.Type) throws -> UInt8? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let uint8 = number.uint8Value - guard NSNumber(value: uint8) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return uint8 - } - - fileprivate func unbox(_ value: Any, as type: UInt16.Type) throws -> UInt16? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let uint16 = number.uint16Value - guard NSNumber(value: uint16) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return uint16 - } - - fileprivate func unbox(_ value: Any, as type: UInt32.Type) throws -> UInt32? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let uint32 = number.uint32Value - guard NSNumber(value: uint32) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return uint32 - } - - fileprivate func unbox(_ value: Any, as type: UInt64.Type) throws -> UInt64? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let uint64 = number.uint64Value - guard NSNumber(value: uint64) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return uint64 - } - - fileprivate func unbox(_ value: Any, as type: Float.Type) throws -> Float? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let float = number.floatValue - guard NSNumber(value: float) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return float - } - - fileprivate func unbox(_ value: Any, as type: Double.Type) throws -> Double? { - if let string = value as? String, string == _plistNull { return nil } - - guard let number = value as? NSNumber, number !== kCFBooleanTrue, number !== kCFBooleanFalse else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - let double = number.doubleValue - guard NSNumber(value: double) == number else { - throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed property list number <\(number)> does not fit in \(type).")) - } - - return double - } - - fileprivate func unbox(_ value: Any, as type: String.Type) throws -> String? { - guard let string = value as? String else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - return string == _plistNull ? nil : string - } - - fileprivate func unbox(_ value: Any, as type: Date.Type) throws -> Date? { - if let string = value as? String, string == _plistNull { return nil } - - guard let date = value as? Date else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - return date - } - - fileprivate func unbox(_ value: Any, as type: Data.Type) throws -> Data? { - if let string = value as? String, string == _plistNull { return nil } - - guard let data = value as? Data else { - throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value) - } - - return data - } - - fileprivate func unbox(_ value: Any, as type: T.Type) throws -> T? { - if type == Date.self || type == NSDate.self { - return try self.unbox(value, as: Date.self) as? T - } else if type == Data.self || type == NSData.self { - return try self.unbox(value, as: Data.self) as? T - } else { - self.storage.push(container: value) - defer { self.storage.popContainer() } - return try type.init(from: self) - } - } -} - -//===----------------------------------------------------------------------===// -// Shared Plist Null Representation -//===----------------------------------------------------------------------===// - -// Since plists do not support null values by default, we will encode them as "$null". -fileprivate let _plistNull = "$null" -fileprivate let _plistNullNSString = NSString(string: _plistNull) - -//===----------------------------------------------------------------------===// -// Shared Key Types -//===----------------------------------------------------------------------===// - -fileprivate struct _PlistKey : CodingKey { - public var stringValue: String - public var intValue: Int? - - public init?(stringValue: String) { - self.stringValue = stringValue - self.intValue = nil - } - - public init?(intValue: Int) { - self.stringValue = "\(intValue)" - self.intValue = intValue - } - - fileprivate init(index: Int) { - self.stringValue = "Index \(index)" - self.intValue = index - } - - fileprivate static let `super` = _PlistKey(stringValue: "super")! -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/String+SpecialCharacters.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/String+SpecialCharacters.swift deleted file mode 100644 index e084323..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/String+SpecialCharacters.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Foundation - -extension String { - - /// Checks whether the string has escaped special character literals or not. - /// - /// This method won't detect an unescaped special character. - /// For example, this method will return true for "\\n" or #"\n"#, but false for "\n" - /// - /// The following are the special character literals that this methods looks for: - /// The escaped special characters \0 (null character), \\ (backslash), - /// \t (horizontal tab), \n (line feed), \r (carriage return), - /// \" (double quotation mark) and \' (single quotation mark), - /// An arbitrary Unicode scalar value, written as \u{n}, - /// where n is a 1–8 digit hexadecimal number (Unicode is discussed in Unicode below) - /// The character sequence "# - /// - /// - Returns: True if the string has any special character literals, false otherwise. - func hasEscapedSpecialCharactersLiteral() -> Bool { - let multilineLiteralAndNumberSign = ##""" - """# - """## - let patterns = [ - // Matches \u{n} where n is a 1–8 digit hexadecimal number - try? NSRegularExpression(pattern: #"\\u\{[a-fA-f0-9]{1,8}\}"#, options: .init()), - try? NSRegularExpression(pattern: #"\0"#, options: .ignoreMetacharacters), - try? NSRegularExpression(pattern: #"\\"#, options: .ignoreMetacharacters), - try? NSRegularExpression(pattern: #"\t"#, options: .ignoreMetacharacters), - try? NSRegularExpression(pattern: #"\n"#, options: .ignoreMetacharacters), - try? NSRegularExpression(pattern: #"\r"#, options: .ignoreMetacharacters), - try? NSRegularExpression(pattern: #"\""#, options: .ignoreMetacharacters), - try? NSRegularExpression(pattern: #"\'"#, options: .ignoreMetacharacters), - try? NSRegularExpression(pattern: multilineLiteralAndNumberSign, options: .ignoreMetacharacters), - ] - let matches = patterns.compactMap { $0?.firstMatch(in: self, options: .init(), range: NSRange.init(location: 0, length: self.count)) } - return matches.count > 0 - } - - - /// This method calculates how many number signs (#) we need to add around a string - /// literal to properly escape its content. - /// - /// Multiple # are needed when the literal contains "#, "##, "### ... - /// - /// - Returns: The number of "number signs(#)" needed around a string literal. - /// When there is no "#, ... return 1 - func numberOfNumberSignsNeeded() -> Int { - let pattern = try! NSRegularExpression(pattern: ##""#{1,}"##, options: .init()) - - let matches = pattern.matches(in: self, options: .init(), range: NSRange.init(location: 0, length: self.count)) - - // If we have "## then the length of the match is 3, - // which is also the number of "number signs (#)" we need to add - // before and after the string literal - return matches.map { $0.range.length }.max() ?? 1 - } -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/View.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/View.swift deleted file mode 100644 index 053bb85..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/View.swift +++ /dev/null @@ -1,891 +0,0 @@ -#if os(iOS) || os(macOS) || os(tvOS) -#if os(macOS) -import Cocoa -#endif -import SceneKit -import SpriteKit -#if os(iOS) || os(tvOS) -import UIKit -#endif -#if os(iOS) || os(macOS) -import WebKit -#endif - -#if os(iOS) || os(tvOS) -public struct ViewImageConfig { - public enum Orientation { - case landscape - case portrait - } - public enum TabletOrientation { - public enum PortraitSplits { - case oneThird - case twoThirds - case full - } - public enum LandscapeSplits { - case oneThird - case oneHalf - case twoThirds - case full - } - case landscape(splitView: LandscapeSplits) - case portrait(splitView: PortraitSplits) - } - - public var safeArea: UIEdgeInsets - public var size: CGSize? - public var traits: UITraitCollection - - public init( - safeArea: UIEdgeInsets = .zero, - size: CGSize? = nil, - traits: UITraitCollection = .init() - ) { - self.safeArea = safeArea - self.size = size - self.traits = traits - } - - #if os(iOS) - public static let iPhoneSe = ViewImageConfig.iPhoneSe(.portrait) - - public static func iPhoneSe(_ orientation: Orientation) -> ViewImageConfig { - let safeArea: UIEdgeInsets - let size: CGSize - switch orientation { - case .landscape: - safeArea = .zero - size = .init(width: 568, height: 320) - case .portrait: - safeArea = .init(top: 20, left: 0, bottom: 0, right: 0) - size = .init(width: 320, height: 568) - } - return .init(safeArea: safeArea, size: size, traits: .iPhoneSe(orientation)) - } - - public static let iPhone8 = ViewImageConfig.iPhone8(.portrait) - - public static func iPhone8(_ orientation: Orientation) -> ViewImageConfig { - let safeArea: UIEdgeInsets - let size: CGSize - switch orientation { - case .landscape: - safeArea = .zero - size = .init(width: 667, height: 375) - case .portrait: - safeArea = .init(top: 20, left: 0, bottom: 0, right: 0) - size = .init(width: 375, height: 667) - } - return .init(safeArea: safeArea, size: size, traits: .iPhone8(orientation)) - } - - public static let iPhone8Plus = ViewImageConfig.iPhone8Plus(.portrait) - - public static func iPhone8Plus(_ orientation: Orientation) -> ViewImageConfig { - let safeArea: UIEdgeInsets - let size: CGSize - switch orientation { - case .landscape: - safeArea = .zero - size = .init(width: 736, height: 414) - case .portrait: - safeArea = .init(top: 20, left: 0, bottom: 0, right: 0) - size = .init(width: 414, height: 736) - } - return .init(safeArea: safeArea, size: size, traits: .iPhone8Plus(orientation)) - } - - public static let iPhoneX = ViewImageConfig.iPhoneX(.portrait) - - public static func iPhoneX(_ orientation: Orientation) -> ViewImageConfig { - let safeArea: UIEdgeInsets - let size: CGSize - switch orientation { - case .landscape: - safeArea = .init(top: 0, left: 44, bottom: 24, right: 44) - size = .init(width: 812, height: 375) - case .portrait: - safeArea = .init(top: 44, left: 0, bottom: 34, right: 0) - size = .init(width: 375, height: 812) - } - return .init(safeArea: safeArea, size: size, traits: .iPhoneX(orientation)) - } - - public static let iPhoneXsMax = ViewImageConfig.iPhoneXsMax(.portrait) - - public static func iPhoneXsMax(_ orientation: Orientation) -> ViewImageConfig { - let safeArea: UIEdgeInsets - let size: CGSize - switch orientation { - case .landscape: - safeArea = .init(top: 0, left: 44, bottom: 24, right: 44) - size = .init(width: 896, height: 414) - case .portrait: - safeArea = .init(top: 44, left: 0, bottom: 34, right: 0) - size = .init(width: 414, height: 896) - } - return .init(safeArea: safeArea, size: size, traits: .iPhoneXsMax(orientation)) - } - - @available(iOS 11.0, *) - public static let iPhoneXr = ViewImageConfig.iPhoneXr(.portrait) - - @available(iOS 11.0, *) - public static func iPhoneXr(_ orientation: Orientation) -> ViewImageConfig { - let safeArea: UIEdgeInsets - let size: CGSize - switch orientation { - case .landscape: - safeArea = .init(top: 0, left: 44, bottom: 24, right: 44) - size = .init(width: 896, height: 414) - case .portrait: - safeArea = .init(top: 44, left: 0, bottom: 34, right: 0) - size = .init(width: 414, height: 896) - } - return .init(safeArea: safeArea, size: size, traits: .iPhoneXr(orientation)) - } - - public static let iPadMini = ViewImageConfig.iPadMini(.landscape) - - public static func iPadMini(_ orientation: Orientation) -> ViewImageConfig { - switch orientation { - case .landscape: - return ViewImageConfig.iPadMini(.landscape(splitView: .full)) - case .portrait: - return ViewImageConfig.iPadMini(.portrait(splitView: .full)) - } - } - - public static func iPadMini(_ orientation: TabletOrientation) -> ViewImageConfig { - let size: CGSize - let traits: UITraitCollection - switch orientation { - case .landscape(let splitView): - switch splitView { - case .oneThird: - size = .init(width: 320, height: 768) - traits = .iPadMini_Compact_SplitView - case .oneHalf: - size = .init(width: 507, height: 768) - traits = .iPadMini_Compact_SplitView - case .twoThirds: - size = .init(width: 694, height: 768) - traits = .iPadMini - case .full: - size = .init(width: 1024, height: 768) - traits = .iPadMini - } - case .portrait(let splitView): - switch splitView { - case .oneThird: - size = .init(width: 320, height: 1024) - traits = .iPadMini_Compact_SplitView - case .twoThirds: - size = .init(width: 438, height: 1024) - traits = .iPadMini_Compact_SplitView - case .full: - size = .init(width: 768, height: 1024) - traits = .iPadMini - } - } - return .init(safeArea: .init(top: 20, left: 0, bottom: 0, right: 0), size: size, traits: traits) - } - - public static let iPadPro10_5 = ViewImageConfig.iPadPro10_5(.landscape) - - public static func iPadPro10_5(_ orientation: Orientation) -> ViewImageConfig { - switch orientation { - case .landscape: - return ViewImageConfig.iPadPro10_5(.landscape(splitView: .full)) - case .portrait: - return ViewImageConfig.iPadPro10_5(.portrait(splitView: .full)) - } - } - - public static func iPadPro10_5(_ orientation: TabletOrientation) -> ViewImageConfig { - let size: CGSize - let traits: UITraitCollection - switch orientation { - case .landscape(let splitView): - switch splitView { - case .oneThird: - size = .init(width: 320, height: 834) - traits = .iPadPro10_5_Compact_SplitView - case .oneHalf: - size = .init(width: 551, height: 834) - traits = .iPadPro10_5_Compact_SplitView - case .twoThirds: - size = .init(width: 782, height: 834) - traits = .iPadPro10_5 - case .full: - size = .init(width: 1112, height: 834) - traits = .iPadPro10_5 - } - case .portrait(let splitView): - switch splitView { - case .oneThird: - size = .init(width: 320, height: 1112) - traits = .iPadPro10_5_Compact_SplitView - case .twoThirds: - size = .init(width: 504, height: 1112) - traits = .iPadPro10_5_Compact_SplitView - case .full: - size = .init(width: 834, height: 1112) - traits = .iPadPro10_5 - } - } - return .init(safeArea: .init(top: 20, left: 0, bottom: 0, right: 0), size: size, traits: traits) - } - - public static let iPadPro11 = ViewImageConfig.iPadPro11(.landscape) - - public static func iPadPro11(_ orientation: Orientation) -> ViewImageConfig { - switch orientation { - case .landscape: - return ViewImageConfig.iPadPro11(.landscape(splitView: .full)) - case .portrait: - return ViewImageConfig.iPadPro11(.portrait(splitView: .full)) - } - } - - public static func iPadPro11(_ orientation: TabletOrientation) -> ViewImageConfig { - let size: CGSize - let traits: UITraitCollection - switch orientation { - case .landscape(let splitView): - switch splitView { - case .oneThird: - size = .init(width: 375, height: 834) - traits = .iPadPro11_Compact_SplitView - case .oneHalf: - size = .init(width: 592, height: 834) - traits = .iPadPro11_Compact_SplitView - case .twoThirds: - size = .init(width: 809, height: 834) - traits = .iPadPro11 - case .full: - size = .init(width: 1194, height: 834) - traits = .iPadPro11 - } - case .portrait(let splitView): - switch splitView { - case .oneThird: - size = .init(width: 320, height: 1194) - traits = .iPadPro11_Compact_SplitView - case .twoThirds: - size = .init(width: 504, height: 1194) - traits = .iPadPro11_Compact_SplitView - case .full: - size = .init(width: 834, height: 1194) - traits = .iPadPro11 - } - } - return .init(safeArea: .init(top: 24, left: 0, bottom: 20, right: 0), size: size, traits: traits) - } - - public static let iPadPro12_9 = ViewImageConfig.iPadPro12_9(.landscape) - - public static func iPadPro12_9(_ orientation: Orientation) -> ViewImageConfig { - switch orientation { - case .landscape: - return ViewImageConfig.iPadPro12_9(.landscape(splitView: .full)) - case .portrait: - return ViewImageConfig.iPadPro12_9(.portrait(splitView: .full)) - } - } - - public static func iPadPro12_9(_ orientation: TabletOrientation) -> ViewImageConfig { - let size: CGSize - let traits: UITraitCollection - switch orientation { - case .landscape(let splitView): - switch splitView { - case .oneThird: - size = .init(width: 375, height: 1024) - traits = .iPadPro12_9_Compact_SplitView - case .oneHalf: - size = .init(width: 678, height: 1024) - traits = .iPadPro12_9 - case .twoThirds: - size = .init(width: 981, height: 1024) - traits = .iPadPro12_9 - case .full: - size = .init(width: 1366, height: 1024) - traits = .iPadPro12_9 - } - - case .portrait(let splitView): - switch splitView { - case .oneThird: - size = .init(width: 375, height: 1366) - traits = .iPadPro12_9_Compact_SplitView - case .twoThirds: - size = .init(width: 639, height: 1366) - traits = .iPadPro12_9_Compact_SplitView - case .full: - size = .init(width: 1024, height: 1366) - traits = .iPadPro12_9 - } - - } - return .init(safeArea: .init(top: 20, left: 0, bottom: 0, right: 0), size: size, traits: traits) - } - #elseif os(tvOS) - public static let tv = ViewImageConfig( - safeArea: .init(top: 60, left: 90, bottom: 60, right: 90), - size: .init(width: 1920, height: 1080), - traits: .init() - ) - public static let tv4K = ViewImageConfig( - safeArea: .init(top: 120, left: 180, bottom: 120, right: 180), - size: .init(width: 3840, height: 2160), - traits: .init() - ) - #endif -} - -extension UITraitCollection { - #if os(iOS) - public static func iPhoneSe(_ orientation: ViewImageConfig.Orientation) - -> UITraitCollection { - let base: [UITraitCollection] = [ -// .init(displayGamut: .SRGB), -// .init(displayScale: 2), - .init(forceTouchCapability: .available), - .init(layoutDirection: .leftToRight), - .init(preferredContentSizeCategory: .medium), - .init(userInterfaceIdiom: .phone) - ] - switch orientation { - case .landscape: - return .init( - traitsFrom: base + [ - .init(horizontalSizeClass: .compact), - .init(verticalSizeClass: .compact) - ] - ) - case .portrait: - return .init( - traitsFrom: base + [ - .init(horizontalSizeClass: .compact), - .init(verticalSizeClass: .regular), - ] - ) - } - } - - public static func iPhone8(_ orientation: ViewImageConfig.Orientation) - -> UITraitCollection { - let base: [UITraitCollection] = [ -// .init(displayGamut: .P3), -// .init(displayScale: 2), - .init(forceTouchCapability: .available), - .init(layoutDirection: .leftToRight), - .init(preferredContentSizeCategory: .medium), - .init(userInterfaceIdiom: .phone) - ] - switch orientation { - case .landscape: - return .init( - traitsFrom: base + [ - .init(horizontalSizeClass: .compact), - .init(verticalSizeClass: .compact) - ] - ) - case .portrait: - return .init( - traitsFrom: base + [ - .init(horizontalSizeClass: .compact), - .init(verticalSizeClass: .regular) - ] - ) - } - } - - public static func iPhone8Plus(_ orientation: ViewImageConfig.Orientation) - -> UITraitCollection { - let base: [UITraitCollection] = [ -// .init(displayGamut: .P3), -// .init(displayScale: 3), - .init(forceTouchCapability: .available), - .init(layoutDirection: .leftToRight), - .init(preferredContentSizeCategory: .medium), - .init(userInterfaceIdiom: .phone) - ] - switch orientation { - case .landscape: - return .init( - traitsFrom: base + [ - .init(horizontalSizeClass: .regular), - .init(verticalSizeClass: .compact) - ] - ) - case .portrait: - return .init( - traitsFrom: base + [ - .init(horizontalSizeClass: .compact), - .init(verticalSizeClass: .regular) - ] - ) - } - } - - public static func iPhoneX(_ orientation: ViewImageConfig.Orientation) - -> UITraitCollection { - let base: [UITraitCollection] = [ -// .init(displayGamut: .P3), -// .init(displayScale: 3), - .init(forceTouchCapability: .available), - .init(layoutDirection: .leftToRight), - .init(preferredContentSizeCategory: .medium), - .init(userInterfaceIdiom: .phone) - ] - switch orientation { - case .landscape: - return .init( - traitsFrom: base + [ - .init(horizontalSizeClass: .compact), - .init(verticalSizeClass: .compact) - ] - ) - case .portrait: - return .init( - traitsFrom: base + [ - .init(horizontalSizeClass: .compact), - .init(verticalSizeClass: .regular) - ] - ) - } - } - - public static func iPhoneXr(_ orientation: ViewImageConfig.Orientation) - -> UITraitCollection { - let base: [UITraitCollection] = [ -// .init(displayGamut: .P3), -// .init(displayScale: 2), - .init(forceTouchCapability: .unavailable), - .init(layoutDirection: .leftToRight), - .init(preferredContentSizeCategory: .medium), - .init(userInterfaceIdiom: .phone) - ] - switch orientation { - case .landscape: - return .init( - traitsFrom: base + [ - .init(horizontalSizeClass: .regular), - .init(verticalSizeClass: .compact) - ] - ) - case .portrait: - return .init( - traitsFrom: base + [ - .init(horizontalSizeClass: .compact), - .init(verticalSizeClass: .regular) - ] - ) - } - } - - public static func iPhoneXsMax(_ orientation: ViewImageConfig.Orientation) - -> UITraitCollection { - let base: [UITraitCollection] = [ -// .init(displayGamut: .P3), -// .init(displayScale: 3), - .init(forceTouchCapability: .available), - .init(layoutDirection: .leftToRight), - .init(preferredContentSizeCategory: .medium), - .init(userInterfaceIdiom: .phone) - ] - switch orientation { - case .landscape: - return .init( - traitsFrom: base + [ - .init(horizontalSizeClass: .regular), - .init(verticalSizeClass: .compact) - ] - ) - case .portrait: - return .init( - traitsFrom: [ - .init(horizontalSizeClass: .compact), - .init(verticalSizeClass: .regular) - ] - ) - } - } - - public static let iPadMini = iPad - public static let iPadMini_Compact_SplitView = iPadCompactSplitView - public static let iPadPro10_5 = iPad - public static let iPadPro10_5_Compact_SplitView = iPadCompactSplitView - public static let iPadPro11 = iPad - public static let iPadPro11_Compact_SplitView = iPadCompactSplitView - public static let iPadPro12_9 = iPad - public static let iPadPro12_9_Compact_SplitView = iPadCompactSplitView - - private static let iPad = UITraitCollection( - traitsFrom: [ -// .init(displayScale: 2), - .init(horizontalSizeClass: .regular), - .init(verticalSizeClass: .regular), - .init(userInterfaceIdiom: .pad) - ] - ) - - private static let iPadCompactSplitView = UITraitCollection( - traitsFrom: [ - .init(horizontalSizeClass: .compact), - .init(verticalSizeClass: .regular), - .init(userInterfaceIdiom: .pad) - ] - ) - #elseif os(tvOS) - // TODO - #endif -} -#endif - -func addImagesForRenderedViews(_ view: View) -> [Async] { - return view.snapshot - .map { async in - [ - Async { callback in - async.run { image in - let imageView = ImageView() - imageView.image = image - imageView.frame = view.frame - #if os(macOS) - view.superview?.addSubview(imageView, positioned: .above, relativeTo: view) - #elseif os(iOS) || os(tvOS) - view.superview?.insertSubview(imageView, aboveSubview: view) - #endif - callback(imageView) - } - } - ] - } - ?? view.subviews.flatMap(addImagesForRenderedViews) -} - -extension View { - var snapshot: Async? { - func inWindow(_ perform: () -> T) -> T { - #if os(macOS) - let superview = self.superview - defer { superview?.addSubview(self) } - let window = ScaledWindow() - window.contentView = NSView() - window.contentView?.addSubview(self) - window.makeKey() - #endif - return perform() - } - #if (os(iOS) && !targetEnvironment(macCatalyst)) || os(tvOS) - if let glkView = self as? GLKView { - return Async(value: inWindow { glkView.snapshot }) - } - #endif - if let scnView = self as? SCNView { - return Async(value: inWindow { scnView.snapshot() }) - } else if let skView = self as? SKView { - if #available(macOS 10.11, *) { - let cgImage = inWindow { skView.texture(from: skView.scene!)!.cgImage() } - #if os(macOS) - let image = Image(cgImage: cgImage, size: skView.bounds.size) - #elseif os(iOS) || os(tvOS) - let image = Image(cgImage: cgImage) - #endif - return Async(value: image) - } else { - fatalError("Taking SKView snapshots requires macOS 10.11 or greater") - } - } - #if os(iOS) || os(macOS) - if let wkWebView = self as? WKWebView { - return Async { callback in - let delegate = NavigationDelegate() - let work = { - if #available(iOS 11.0, macOS 10.13, *) { - inWindow { - guard wkWebView.frame.width != 0, wkWebView.frame.height != 0 else { - callback(Image()) - return - } - wkWebView.takeSnapshot(with: nil) { image, _ in - _ = delegate - callback(image!) - } - } - } else { - #if os(iOS) - fatalError("Taking WKWebView snapshots requires iOS 11.0 or greater") - #elseif os(macOS) - fatalError("Taking WKWebView snapshots requires macOS 10.13 or greater") - #endif - } - } - - if wkWebView.isLoading { - delegate.didFinish = work - wkWebView.navigationDelegate = delegate - } else { - work() - } - } - } - #endif - return nil - } - #if os(iOS) || os(tvOS) - func asImage() -> Image { - let renderer = UIGraphicsImageRenderer(bounds: bounds) - return renderer.image { rendererContext in - layer.render(in: rendererContext.cgContext) - } - } - #endif -} - -#if os(iOS) || os(macOS) -private final class NavigationDelegate: NSObject, WKNavigationDelegate { - var didFinish: () -> Void - - init(didFinish: @escaping () -> Void = {}) { - self.didFinish = didFinish - } - - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - webView.evaluateJavaScript("document.readyState") { _, _ in - self.didFinish() - } - } -} -#endif - -#if os(iOS) || os(tvOS) -extension UIApplication { - static var sharedIfAvailable: UIApplication? { - let sharedSelector = NSSelectorFromString("sharedApplication") - guard UIApplication.responds(to: sharedSelector) else { - return nil - } - - let shared = UIApplication.perform(sharedSelector) - return shared?.takeUnretainedValue() as! UIApplication? - } -} - -func prepareView( - config: ViewImageConfig, - drawHierarchyInKeyWindow: Bool, - traits: UITraitCollection, - view: UIView, - viewController: UIViewController - ) -> () -> Void { - let size = config.size ?? viewController.view.frame.size - view.frame.size = size - if view != viewController.view { - viewController.view.bounds = view.bounds - viewController.view.addSubview(view) - } - let traits = UITraitCollection(traitsFrom: [config.traits, traits]) - let window: UIWindow - if drawHierarchyInKeyWindow { - guard let keyWindow = getKeyWindow() else { - fatalError("'drawHierarchyInKeyWindow' requires tests to be run in a host application") - } - window = keyWindow - window.frame.size = size - } else { - window = Window( - config: .init(safeArea: config.safeArea, size: config.size ?? size, traits: traits), - viewController: viewController - ) - } - let dispose = add(traits: traits, viewController: viewController, to: window) - - if size.width == 0 || size.height == 0 { - // Try to call sizeToFit() if the view still has invalid size - view.sizeToFit() - view.setNeedsLayout() - view.layoutIfNeeded() - } - - return dispose -} - -func snapshotView( - config: ViewImageConfig, - drawHierarchyInKeyWindow: Bool, - traits: UITraitCollection, - view: UIView, - viewController: UIViewController - ) - -> Async { - let initialFrame = view.frame - let dispose = prepareView( - config: config, - drawHierarchyInKeyWindow: drawHierarchyInKeyWindow, - traits: traits, - view: view, - viewController: viewController - ) - // NB: Avoid safe area influence. - if config.safeArea == .zero { view.frame.origin = .init(x: offscreen, y: offscreen) } - - return (view.snapshot ?? Async { callback in - addImagesForRenderedViews(view).sequence().run { views in - callback( - renderer(bounds: view.bounds, for: traits).image { ctx in - if drawHierarchyInKeyWindow { - view.drawHierarchy(in: view.bounds, afterScreenUpdates: true) - } else { - view.layer.render(in: ctx.cgContext) - } - } - ) - views.forEach { $0.removeFromSuperview() } - view.frame = initialFrame - } - }).map { dispose(); return $0 } -} - -private let offscreen: CGFloat = 10_000 - -func renderer(bounds: CGRect, for traits: UITraitCollection) -> UIGraphicsImageRenderer { - let renderer: UIGraphicsImageRenderer - if #available(iOS 11.0, tvOS 11.0, *) { - renderer = UIGraphicsImageRenderer(bounds: bounds, format: .init(for: traits)) - } else { - renderer = UIGraphicsImageRenderer(bounds: bounds) - } - return renderer -} - -private func add(traits: UITraitCollection, viewController: UIViewController, to window: UIWindow) -> () -> Void { - let rootViewController: UIViewController - if viewController != window.rootViewController { - rootViewController = UIViewController() - rootViewController.view.backgroundColor = .clear - rootViewController.view.frame = window.frame - rootViewController.view.translatesAutoresizingMaskIntoConstraints = - viewController.view.translatesAutoresizingMaskIntoConstraints - rootViewController.preferredContentSize = rootViewController.view.frame.size - viewController.view.frame = rootViewController.view.frame - rootViewController.view.addSubview(viewController.view) - if viewController.view.translatesAutoresizingMaskIntoConstraints { - viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - } else { - NSLayoutConstraint.activate([ - viewController.view.topAnchor.constraint(equalTo: rootViewController.view.topAnchor), - viewController.view.bottomAnchor.constraint(equalTo: rootViewController.view.bottomAnchor), - viewController.view.leadingAnchor.constraint(equalTo: rootViewController.view.leadingAnchor), - viewController.view.trailingAnchor.constraint(equalTo: rootViewController.view.trailingAnchor), - ]) - } - rootViewController.addChild(viewController) - } else { - rootViewController = viewController - } - rootViewController.setOverrideTraitCollection(traits, forChild: viewController) - viewController.didMove(toParent: rootViewController) - - window.rootViewController = rootViewController - - rootViewController.beginAppearanceTransition(true, animated: false) - rootViewController.endAppearanceTransition() - - rootViewController.view.setNeedsLayout() - rootViewController.view.layoutIfNeeded() - - viewController.view.setNeedsLayout() - viewController.view.layoutIfNeeded() - - return { - rootViewController.beginAppearanceTransition(false, animated: false) - rootViewController.endAppearanceTransition() - window.rootViewController = nil - } -} - -private func getKeyWindow() -> UIWindow? { - var window: UIWindow? - if #available(iOS 13.0, *) { - window = UIApplication.sharedIfAvailable?.windows.first { $0.isKeyWindow } - } else { - window = UIApplication.sharedIfAvailable?.keyWindow - } - return window -} - -private final class Window: UIWindow { - var config: ViewImageConfig - - init(config: ViewImageConfig, viewController: UIViewController) { - let size = config.size ?? viewController.view.bounds.size - self.config = config - super.init(frame: .init(origin: .zero, size: size)) - - // NB: Safe area renders inaccurately for UI{Navigation,TabBar}Controller. - // Fixes welcome! - if viewController is UINavigationController { - self.frame.size.height -= self.config.safeArea.top - self.config.safeArea.top = 0 - } else if let viewController = viewController as? UITabBarController { - self.frame.size.height -= self.config.safeArea.bottom - self.config.safeArea.bottom = 0 - if viewController.selectedViewController is UINavigationController { - self.frame.size.height -= self.config.safeArea.top - self.config.safeArea.top = 0 - } - } - self.isHidden = false - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @available(iOS 11.0, *) - override var safeAreaInsets: UIEdgeInsets { - #if os(iOS) - let removeTopInset = self.config.safeArea == .init(top: 20, left: 0, bottom: 0, right: 0) - && self.rootViewController?.prefersStatusBarHidden ?? false - if removeTopInset { return .zero } - #endif - return self.config.safeArea - } -} -#endif - -#if os(macOS) -import Cocoa - -private final class ScaledWindow: NSWindow { - override var backingScaleFactor: CGFloat { - return 2 - } -} -#endif -#endif - -extension Array { - func sequence() -> Async<[A]> where Element == Async { - guard !self.isEmpty else { return Async(value: []) } - return Async<[A]> { callback in - var result = [A?](repeating: nil, count: self.count) - result.reserveCapacity(self.count) - var count = 0 - zip(self.indices, self).forEach { idx, async in - async.run { - result[idx] = $0 - count += 1 - if count == self.count { - callback(result as! [A]) - } - } - } - } - } -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/XCTAttachment.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/XCTAttachment.swift deleted file mode 100644 index 312d8bc..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Common/XCTAttachment.swift +++ /dev/null @@ -1,8 +0,0 @@ -#if os(Linux) -import Foundation - -public struct XCTAttachment { - public init(data: Data) {} - public init(data: Data, uniformTypeIdentifier: String) {} -} -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Diff.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Diff.swift deleted file mode 100644 index 20d264c..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Diff.swift +++ /dev/null @@ -1,125 +0,0 @@ -import Foundation - -struct Difference { - enum Which { - case first - case second - case both - } - - let elements: [A] - let which: Which -} - -func diff(_ fst: [A], _ snd: [A]) -> [Difference] { - var idxsOf = [A: [Int]]() - fst.enumerated().forEach { idxsOf[$1, default: []].append($0) } - - let sub = snd.enumerated().reduce((overlap: [Int: Int](), fst: 0, snd: 0, len: 0)) { sub, sndPair in - (idxsOf[sndPair.element] ?? []) - .reduce((overlap: [Int: Int](), fst: sub.fst, snd: sub.snd, len: sub.len)) { innerSub, fstIdx in - - var newOverlap = innerSub.overlap - newOverlap[fstIdx] = (sub.overlap[fstIdx - 1] ?? 0) + 1 - - if let newLen = newOverlap[fstIdx], newLen > sub.len { - return (newOverlap, fstIdx - newLen + 1, sndPair.offset - newLen + 1, newLen) - } - return (newOverlap, innerSub.fst, innerSub.snd, innerSub.len) - } - } - let (_, fstIdx, sndIdx, len) = sub - - if len == 0 { - let fstDiff = fst.isEmpty ? [] : [Difference(elements: fst, which: .first)] - let sndDiff = snd.isEmpty ? [] : [Difference(elements: snd, which: .second)] - return fstDiff + sndDiff - } else { - let fstDiff = diff(Array(fst.prefix(upTo: fstIdx)), Array(snd.prefix(upTo: sndIdx))) - let midDiff = [Difference(elements: Array(fst.suffix(from: fstIdx).prefix(len)), which: .both)] - let lstDiff = diff(Array(fst.suffix(from: fstIdx + len)), Array(snd.suffix(from: sndIdx + len))) - return fstDiff + midDiff + lstDiff - } -} - -let minus = "−" -let plus = "+" -private let figureSpace = "\u{2007}" - -struct Hunk { - let fstIdx: Int - let fstLen: Int - let sndIdx: Int - let sndLen: Int - let lines: [String] - - var patchMark: String { - let fstMark = "\(minus)\(fstIdx + 1),\(fstLen)" - let sndMark = "\(plus)\(sndIdx + 1),\(sndLen)" - return "@@ \(fstMark) \(sndMark) @@" - } - - // Semigroup - - static func +(lhs: Hunk, rhs: Hunk) -> Hunk { - return Hunk( - fstIdx: lhs.fstIdx + rhs.fstIdx, - fstLen: lhs.fstLen + rhs.fstLen, - sndIdx: lhs.sndIdx + rhs.sndIdx, - sndLen: lhs.sndLen + rhs.sndLen, - lines: lhs.lines + rhs.lines - ) - } - - // Monoid - - init(fstIdx: Int = 0, fstLen: Int = 0, sndIdx: Int = 0, sndLen: Int = 0, lines: [String] = []) { - self.fstIdx = fstIdx - self.fstLen = fstLen - self.sndIdx = sndIdx - self.sndLen = sndLen - self.lines = lines - } - - init(idx: Int = 0, len: Int = 0, lines: [String] = []) { - self.init(fstIdx: idx, fstLen: len, sndIdx: idx, sndLen: len, lines: lines) - } -} - -func chunk(diff diffs: [Difference], context ctx: Int = 4) -> [Hunk] { - func prepending(_ prefix: String) -> (String) -> String { - return { prefix + $0 + ($0.hasSuffix(" ") ? "ÂŹ" : "") } - } - let changed: (Hunk) -> Bool = { $0.lines.contains(where: { $0.hasPrefix(minus) || $0.hasPrefix(plus) }) } - - let (hunk, hunks) = diffs - .reduce((current: Hunk(), hunks: [Hunk]())) { cursor, diff in - let (current, hunks) = cursor - let len = diff.elements.count - - switch diff.which { - case .both where len > ctx * 2: - let hunk = current + Hunk(len: ctx, lines: diff.elements.prefix(ctx).map(prepending(figureSpace))) - let next = Hunk( - fstIdx: current.fstIdx + current.fstLen + len - ctx, - fstLen: ctx, - sndIdx: current.sndIdx + current.sndLen + len - ctx, - sndLen: ctx, - lines: (diff.elements.suffix(ctx) as ArraySlice).map(prepending(figureSpace)) - ) - return (next, changed(hunk) ? hunks + [hunk] : hunks) - case .both where current.lines.isEmpty: - let lines = (diff.elements.suffix(ctx) as ArraySlice).map(prepending(figureSpace)) - let count = lines.count - return (current + Hunk(idx: len - count, len: count, lines: lines), hunks) - case .both: - return (current + Hunk(len: len, lines: diff.elements.map(prepending(figureSpace))), hunks) - case .first: - return (current + Hunk(fstLen: len, lines: diff.elements.map(prepending(minus))), hunks) - case .second: - return (current + Hunk(sndLen: len, lines: diff.elements.map(prepending(plus))), hunks) - } - } - - return changed(hunk) ? hunks + [hunk] : hunks -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Diffing.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Diffing.swift deleted file mode 100644 index 663271a..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Diffing.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation -import XCTest - -/// The ability to compare `Value`s and convert them to and from `Data`. -public struct Diffing { - /// Converts a value _to_ data. - public var toData: (Value) -> Data - - /// Produces a value _from_ data. - public var fromData: (Data) -> Value - - /// Compares two values. If the values do not match, returns a failure message and artifacts describing the failure. - public var diff: (Value, Value) -> (String, [XCTAttachment])? - - /// Creates a new `Diffing` on `Value`. - /// - /// - Parameters: - /// - toData: A function used to convert a value _to_ data. - /// - value: A value to convert into data. - /// - fromData: A function used to produce a value _from_ data. - /// - data: Data to convert into a value. - /// - diff: A function used to compare two values. If the values do not match, returns a failure message and artifacts describing the failure. - /// - lhs: A value to compare. - /// - rhs: Another value to compare. - public init( - toData: @escaping (_ value: Value) -> Data, - fromData: @escaping (_ data: Data) -> Value, - diff: @escaping (_ lhs: Value, _ rhs: Value) -> (String, [XCTAttachment])? - ) { - self.toData = toData - self.fromData = fromData - self.diff = diff - } -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Extensions/Wait.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Extensions/Wait.swift deleted file mode 100644 index 71316df..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Extensions/Wait.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation -import XCTest - -extension Snapshotting { - /// 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). - /// - Parameters: - /// - duration: The amount of time to wait before taking the snapshot. - /// - strategy: The snapshot to invoke after the specified amount of time has passed. - public static func wait( - for duration: TimeInterval, - on strategy: Snapshotting - ) -> Snapshotting { - return Snapshotting( - pathExtension: strategy.pathExtension, - diffing: strategy.diffing, - asyncSnapshot: { value in - Async { callback in - let expectation = XCTestExpectation(description: "Wait") - DispatchQueue.main.asyncAfter(deadline: .now() + duration) { - expectation.fulfill() - } - _ = XCTWaiter.wait(for: [expectation], timeout: duration + 1) - strategy.snapshot(value).run(callback) - } - }) - } -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/SnapshotTestCase.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/SnapshotTestCase.swift deleted file mode 100644 index ba93752..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/SnapshotTestCase.swift +++ /dev/null @@ -1,4 +0,0 @@ -import XCTest - -@available(swift, obsoleted: 5.0, renamed: "XCTestCase", message: "Please use XCTestCase instead") -public typealias SnapshotTestCase = XCTestCase diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting.swift deleted file mode 100644 index c595953..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting.swift +++ /dev/null @@ -1,106 +0,0 @@ -import Foundation -import XCTest - -/// A type representing the ability to transform a snapshottable value into a diffable format (like text or an image) for snapshot testing. -public struct Snapshotting { - /// The path extension applied to references saved to disk. - public var pathExtension: String? - - /// How the snapshot format is diffed and converted to and from data. - public var diffing: Diffing - - /// How a value is transformed into a diffable snapshot format. - public var snapshot: (Value) -> Async - - /// Creates a snapshot strategy. - /// - /// - Parameters: - /// - pathExtension: The path extension applied to references saved to disk. - /// - diffing: How to diff and convert the snapshot format to and from data. - /// - snapshot: An asynchronous transform function from a value into a diffable snapshot format. - /// - value: A value to be converted. - public init( - pathExtension: String?, - diffing: Diffing, - asyncSnapshot: @escaping (_ value: Value) -> Async - ) { - self.pathExtension = pathExtension - self.diffing = diffing - self.snapshot = asyncSnapshot - } - - /// Creates a snapshot strategy. - /// - /// - Parameters: - /// - pathExtension: The path extension applied to references saved to disk. - /// - diffing: How to diff and convert the snapshot format to and from data. - /// - snapshot: A transform function from a value into a diffable snapshot format. - /// - value: A snapshot value to be converted. - public init( - pathExtension: String?, - diffing: Diffing, - snapshot: @escaping (_ value: Value) -> Format - ) { - self.init(pathExtension: pathExtension, diffing: diffing) { - Async(value: snapshot($0)) - } - } - - /// Transforms a strategy on `Value`s into a strategy on `NewValue`s through a function `(NewValue) -> Value`. - /// - /// 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`. - /// - /// A simple example of this is to `pullback` the snapshot strategy on `UIView`s to work on `UIViewController`s: - /// - /// let strategy = Snapshotting.image.pullback { (vc: UIViewController) in - /// return vc.view - /// } - /// - /// 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. - /// - /// Nearly every snapshot strategy provided in this library is a pullback of some base strategy, which shows just how important this operation is. - /// - /// - Parameters: - /// - transform: A transform function from `NewValue` into `Value`. - /// - otherValue: A value to be transformed. - public func pullback(_ transform: @escaping (_ otherValue: NewValue) -> Value) -> Snapshotting { - return self.asyncPullback { newValue in Async(value: transform(newValue)) } - } - - /// Transforms a strategy on `Value`s into a strategy on `NewValue`s through a function `(NewValue) -> Async`. - /// - /// 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. - /// - /// - Parameters: - /// - transform: A transform function from `NewValue` into `Async`. - /// - otherValue: A value to be transformed. - public func asyncPullback(_ transform: @escaping (_ otherValue: NewValue) -> Async) - -> Snapshotting { - - return Snapshotting( - pathExtension: self.pathExtension, - diffing: self.diffing - ) { newValue in - return .init { callback in - transform(newValue).run { value in - self.snapshot(value).run { snapshot in - callback(snapshot) - } - } - } - } - } -} - -/// A snapshot strategy where the type being snapshot is also a diffable type. -public typealias SimplySnapshotting = Snapshotting - -extension Snapshotting where Value == Format { - public init(pathExtension: String?, diffing: Diffing) { - self.init( - pathExtension: pathExtension, - diffing: diffing, - snapshot: { $0 } - ) - } -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Any.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Any.swift deleted file mode 100644 index 2254190..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Any.swift +++ /dev/null @@ -1,142 +0,0 @@ -import Foundation - -extension Snapshotting where Format == String { - /// A snapshot strategy for comparing any structure based on a sanitized text dump. - public static var dump: Snapshotting { - return SimplySnapshotting.lines.pullback { snap($0) } - } -} - -private func snap(_ value: T, name: String? = nil, indent: Int = 0) -> String { - let indentation = String(repeating: " ", count: indent) - let mirror = Mirror(reflecting: value) - var children = mirror.children - let count = children.count - let bullet = count == 0 ? "-" : "▿" - - let description: String - switch (value, mirror.displayStyle) { - case (_, .collection?): - description = count == 1 ? "1 element" : "\(count) elements" - case (_, .dictionary?): - description = count == 1 ? "1 key/value pair" : "\(count) key/value pairs" - children = sort(children) - case (_, .set?): - description = count == 1 ? "1 member" : "\(count) members" - children = sort(children) - case (_, .tuple?): - description = count == 1 ? "(1 element)" : "(\(count) elements)" - case (_, .optional?): - let subjectType = String(describing: mirror.subjectType) - .replacingOccurrences(of: " #\\d+", with: "", options: .regularExpression) - description = count == 0 ? "\(subjectType).none" : "\(subjectType)" - case (let value as AnySnapshotStringConvertible, _) where type(of: value).renderChildren: - description = value.snapshotDescription - case (let value as AnySnapshotStringConvertible, _): - return "\(indentation)- \(name.map { "\($0): " } ?? "")\(value.snapshotDescription)\n" - case (let value as CustomStringConvertible, _): - description = value.description - case (_, .class?), (_, .struct?): - description = String(describing: mirror.subjectType) - .replacingOccurrences(of: " #\\d+", with: "", options: .regularExpression) - children = sort(children) - case (_, .enum?): - let subjectType = String(describing: mirror.subjectType) - .replacingOccurrences(of: " #\\d+", with: "", options: .regularExpression) - description = count == 0 ? "\(subjectType).\(value)" : "\(subjectType)" - case (let value, _): - description = String(describing: value) - } - - let lines = ["\(indentation)\(bullet) \(name.map { "\($0): " } ?? "")\(description)\n"] - + children.map { snap($1, name: $0, indent: indent + 2) } - - return lines.joined() -} - -private func sort(_ children: Mirror.Children) -> Mirror.Children { - return .init( - children - .map({ (child: $0, snap: snap($0)) }) - .sorted(by: { $0.snap < $1.snap }) - .map({ $0.child }) - ) -} - -/// A type with a customized snapshot dump representation. -/// -/// Types that conform to the `AnySnapshotStringConvertible` protocol can provide their own representation to be used when converting an instance to a `dump`-based snapshot. -public protocol AnySnapshotStringConvertible { - /// Whether or not to dump child nodes (defaults to `false`). - static var renderChildren: Bool { get } - - /// A textual snapshot dump representation of this instance. - var snapshotDescription: String { get } -} - -extension AnySnapshotStringConvertible { - public static var renderChildren: Bool { - return false - } -} - -extension Character: AnySnapshotStringConvertible { - public var snapshotDescription: String { - return self.debugDescription - } -} - -extension Data: AnySnapshotStringConvertible { - public var snapshotDescription: String { - return self.debugDescription - } -} - -extension Date: AnySnapshotStringConvertible { - public var snapshotDescription: String { - return snapshotDateFormatter.string(from: self) - } -} - -extension NSObject: AnySnapshotStringConvertible { - #if canImport(ObjectiveC) - @objc open var snapshotDescription: String { - return purgePointers(self.debugDescription) - } - #else - open var snapshotDescription: String { - return purgePointers(self.debugDescription) - } - #endif -} - -extension String: AnySnapshotStringConvertible { - public var snapshotDescription: String { - return self.debugDescription - } -} - -extension Substring: AnySnapshotStringConvertible { - public var snapshotDescription: String { - return self.debugDescription - } -} - -extension URL: AnySnapshotStringConvertible { - public var snapshotDescription: String { - return self.debugDescription - } -} - -private let snapshotDateFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" - formatter.calendar = Calendar(identifier: .gregorian) - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(abbreviation: "UTC") - return formatter -}() - -func purgePointers(_ string: String) -> String { - return string.replacingOccurrences(of: ":?\\s*0x[\\da-f]+(\\s*)", with: "$1", options: .regularExpression) -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CALayer.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CALayer.swift deleted file mode 100644 index d11e37f..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CALayer.swift +++ /dev/null @@ -1,49 +0,0 @@ -#if os(macOS) -import Cocoa - -extension Snapshotting where Value == CALayer, Format == NSImage { - /// A snapshot strategy for comparing layers based on pixel equality. - public static var image: Snapshotting { - return .image(precision: 1) - } - - /// A snapshot strategy for comparing layers based on pixel equality. - /// - /// - Parameter precision: The percentage of pixels that must match. - public static func image(precision: Float) -> Snapshotting { - return SimplySnapshotting.image(precision: precision).pullback { layer in - let image = NSImage(size: layer.bounds.size) - image.lockFocus() - let context = NSGraphicsContext.current!.cgContext - layer.setNeedsLayout() - layer.layoutIfNeeded() - layer.render(in: context) - image.unlockFocus() - return image - } - } -} -#elseif os(iOS) || os(tvOS) -import UIKit - -extension Snapshotting where Value == CALayer, Format == UIImage { - /// A snapshot strategy for comparing layers based on pixel equality. - public static var image: Snapshotting { - return .image() - } - - /// A snapshot strategy for comparing layers based on pixel equality. - /// - /// - Parameter precision: The percentage of pixels that must match. - public static func image(precision: Float = 1, traits: UITraitCollection = .init()) - -> Snapshotting { - return SimplySnapshotting.image(precision: precision, scale: traits.displayScale).pullback { layer in - renderer(bounds: layer.bounds, for: traits).image { ctx in - layer.setNeedsLayout() - layer.layoutIfNeeded() - layer.render(in: ctx.cgContext) - } - } - } -} -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CGPath.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CGPath.swift deleted file mode 100644 index d7ee7df..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CGPath.swift +++ /dev/null @@ -1,126 +0,0 @@ -#if os(macOS) -import Cocoa - -extension Snapshotting where Value == CGPath, Format == NSImage { - /// A snapshot strategy for comparing bezier paths based on pixel equality. - public static var image: Snapshotting { - return .image() - } - - /// A snapshot strategy for comparing bezier paths based on pixel equality. - /// - /// - Parameter precision: The percentage of pixels that must match. - public static func image(precision: Float = 1, drawingMode: CGPathDrawingMode = .eoFill) -> Snapshotting { - return SimplySnapshotting.image(precision: precision).pullback { path in - let bounds = path.boundingBoxOfPath - var transform = CGAffineTransform(translationX: -bounds.origin.x, y: -bounds.origin.y) - let path = path.copy(using: &transform)! - - let image = NSImage(size: bounds.size) - image.lockFocus() - let context = NSGraphicsContext.current!.cgContext - - context.addPath(path) - context.drawPath(using: drawingMode) - image.unlockFocus() - return image - } - } -} -#elseif os(iOS) || os(tvOS) -import UIKit - -extension Snapshotting where Value == CGPath, Format == UIImage { - /// A snapshot strategy for comparing bezier paths based on pixel equality. - public static var image: Snapshotting { - return .image() - } - - /// A snapshot strategy for comparing bezier paths based on pixel equality. - /// - /// - Parameter precision: The percentage of pixels that must match. - public static func image(precision: Float = 1, scale: CGFloat = 1, drawingMode: CGPathDrawingMode = .eoFill) -> Snapshotting { - return SimplySnapshotting.image(precision: precision, scale: scale).pullback { path in - let bounds = path.boundingBoxOfPath - let format: UIGraphicsImageRendererFormat - if #available(iOS 11.0, tvOS 11.0, *) { - format = UIGraphicsImageRendererFormat.preferred() - } else { - format = UIGraphicsImageRendererFormat.default() - } - format.scale = scale - return UIGraphicsImageRenderer(bounds: bounds, format: format).image { ctx in - let cgContext = ctx.cgContext - cgContext.addPath(path) - cgContext.drawPath(using: drawingMode) - } - } - } -} -#endif - -#if os(macOS) || os(iOS) || os(tvOS) -@available(iOS 11.0, OSX 10.13, tvOS 11.0, *) -extension Snapshotting where Value == CGPath, Format == String { - /// A snapshot strategy for comparing bezier paths based on element descriptions. - public static var elementsDescription: Snapshotting { - return .elementsDescription(numberFormatter: defaultNumberFormatter) - } - - /// A snapshot strategy for comparing bezier paths based on element descriptions. - /// - /// - Parameter numberFormatter: The number formatter used for formatting points. - public static func elementsDescription(numberFormatter: NumberFormatter) -> Snapshotting { - let namesByType: [CGPathElementType: String] = [ - .moveToPoint: "MoveTo", - .addLineToPoint: "LineTo", - .addQuadCurveToPoint: "QuadCurveTo", - .addCurveToPoint: "CurveTo", - .closeSubpath: "Close", - ] - - let numberOfPointsByType: [CGPathElementType: Int] = [ - .moveToPoint: 1, - .addLineToPoint: 1, - .addQuadCurveToPoint: 2, - .addCurveToPoint: 3, - .closeSubpath: 0, - ] - - return SimplySnapshotting.lines.pullback { path in - var string: String = "" - - path.applyWithBlock { elementPointer in - let element = elementPointer.pointee - let name = namesByType[element.type] ?? "Unknown" - - if element.type == .moveToPoint && !string.isEmpty { - string += "\n" - } - - string += name - - if let numberOfPoints = numberOfPointsByType[element.type] { - let points = UnsafeBufferPointer(start: element.points, count: numberOfPoints) - string += " " + points.map { point in - let x = numberFormatter.string(from: point.x as NSNumber)! - let y = numberFormatter.string(from: point.y as NSNumber)! - return "(\(x), \(y))" - }.joined(separator: " ") - } - - string += "\n" - } - - return string - } - } -} - -private let defaultNumberFormatter: NumberFormatter = { - let numberFormatter = NumberFormatter() - numberFormatter.minimumFractionDigits = 1 - numberFormatter.maximumFractionDigits = 3 - return numberFormatter -}() -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CaseIterable.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CaseIterable.swift deleted file mode 100644 index 1a6330e..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/CaseIterable.swift +++ /dev/null @@ -1,25 +0,0 @@ -extension Snapshotting where Value: CaseIterable, Format == String { - /// A strategy for snapshotting the output for every input of a function. The format of the snapshot - /// is a comma-separated value (CSV) file that shows the mapping of inputs to outputs. - /// - /// Parameter witness: A snapshotting value on the output of the function to be snapshot. - /// Returns: A snapshot strategy on functions (Value) -> A that feeds every possible input into the - /// function and records the output into a CSV file. - public static func `func`(into witness: Snapshotting) -> Snapshotting<(Value) -> A, Format> { - var snapshotting = Snapshotting.lines.asyncPullback { (f: (Value) -> A) in - Value.allCases.map { input in - witness.snapshot(f(input)) - .map { (input, $0) } - } - .sequence() - .map { rows in - rows.map { "\"\($0)\",\"\($1)\"" } - .joined(separator: "\n") - } - } - - snapshotting.pathExtension = "csv" - - return snapshotting - } -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Codable.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Codable.swift deleted file mode 100644 index ed3e329..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Codable.swift +++ /dev/null @@ -1,40 +0,0 @@ -import Foundation - -extension Snapshotting where Value: Encodable, Format == String { - /// A snapshot strategy for comparing encodable structures based on their JSON representation. - @available(iOS 11.0, macOS 10.13, tvOS 11.0, *) - public static var json: Snapshotting { - let encoder = JSONEncoder() - encoder.outputFormatting = [.prettyPrinted, .sortedKeys] - return .json(encoder) - } - - /// A snapshot strategy for comparing encodable structures based on their JSON representation. - /// - /// - Parameter encoder: A JSON encoder. - public static func json(_ encoder: JSONEncoder) -> Snapshotting { - var snapshotting = SimplySnapshotting.lines.pullback { (encodable: Value) in - try! String(decoding: encoder.encode(encodable), as: UTF8.self) - } - snapshotting.pathExtension = "json" - return snapshotting - } - - /// A snapshot strategy for comparing encodable structures based on their property list representation. - public static var plist: Snapshotting { - let encoder = PropertyListEncoder() - encoder.outputFormat = .xml - return .plist(encoder) - } - - /// A snapshot strategy for comparing encodable structures based on their property list representation. - /// - /// - Parameter encoder: A property list encoder. - public static func plist(_ encoder: PropertyListEncoder) -> Snapshotting { - var snapshotting = SimplySnapshotting.lines.pullback { (encodable: Value) in - try! String(decoding: encoder.encode(encodable), as: UTF8.self) - } - snapshotting.pathExtension = "plist" - return snapshotting - } -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Data.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Data.swift deleted file mode 100644 index d28778f..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Data.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Foundation -import XCTest - -extension Snapshotting where Value == Data, Format == Data { - public static var data: Snapshotting { - return .init( - pathExtension: nil, - diffing: .init(toData: { $0 }, fromData: { $0 }) { old, new in - guard old != new else { return nil } - let message = old.count == new.count - ? "Expected data to match" - : "Expected \(new) to match \(old)" - return (message, []) - } - ) - } -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Description.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Description.swift deleted file mode 100644 index 41641d2..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/Description.swift +++ /dev/null @@ -1,7 +0,0 @@ -extension Snapshotting where Format == String { - /// A snapshot strategy that captures a value's textual description from `String`'s `init(description:)` - /// initializer. - public static var description: Snapshotting { - return SimplySnapshotting.lines.pullback(String.init(describing:)) - } -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSBezierPath.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSBezierPath.swift deleted file mode 100644 index d9d5def..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSBezierPath.swift +++ /dev/null @@ -1,93 +0,0 @@ -#if os(macOS) -import Cocoa - -extension Snapshotting where Value == NSBezierPath, Format == NSImage { - /// A snapshot strategy for comparing bezier paths based on pixel equality. - public static var image: Snapshotting { - return .image() - } - - /// A snapshot strategy for comparing bezier paths based on pixel equality. - /// - /// - Parameter precision: The percentage of pixels that must match. - public static func image(precision: Float = 1) -> Snapshotting { - return SimplySnapshotting.image(precision: precision).pullback { path in - // Move path info frame: - let bounds = path.bounds - let transform = AffineTransform(translationByX: -bounds.origin.x, byY: -bounds.origin.y) - path.transform(using: transform) - - let image = NSImage(size: path.bounds.size) - image.lockFocus() - path.fill() - image.unlockFocus() - return image - } - } -} - -extension Snapshotting where Value == NSBezierPath, Format == String { - /// A snapshot strategy for comparing bezier paths based on pixel equality. - @available(iOS 11.0, *) - public static var elementsDescription: Snapshotting { - return .elementsDescription(numberFormatter: defaultNumberFormatter) - } - - /// A snapshot strategy for comparing bezier paths based on pixel equality. - /// - /// - Parameter numberFormatter: The number formatter used for formatting points. - @available(iOS 11.0, *) - public static func elementsDescription(numberFormatter: NumberFormatter) -> Snapshotting { - let namesByType: [NSBezierPath.ElementType: String] = [ - .moveTo: "MoveTo", - .lineTo: "LineTo", - .curveTo: "CurveTo", - .closePath: "Close", - ] - - let numberOfPointsByType: [NSBezierPath.ElementType: Int] = [ - .moveTo: 1, - .lineTo: 1, - .curveTo: 3, - .closePath: 0, - ] - - return SimplySnapshotting.lines.pullback { path in - var string: String = "" - - var elementPoints = [CGPoint](repeating: .zero, count: 3) - for elementIndex in 0.. Diffing { - return .init( - toData: { NSImagePNGRepresentation($0)! }, - fromData: { NSImage(data: $0)! } - ) { old, new in - guard !compare(old, new, precision: precision) else { return nil } - let difference = SnapshotTesting.diff(old, new) - let message = new.size == old.size - ? "Newly-taken snapshot does not match reference." - : "Newly-taken snapshot@\(new.size) does not match reference@\(old.size)." - return ( - message, - [XCTAttachment(image: old), XCTAttachment(image: new), XCTAttachment(image: difference)] - ) - } - } -} - -extension Snapshotting where Value == NSImage, Format == NSImage { - /// A snapshot strategy for comparing images based on pixel equality. - public static var image: Snapshotting { - return .image(precision: 1) - } - - /// A snapshot strategy for comparing images based on pixel equality. - /// - /// - Parameter precision: The percentage of pixels that must match. - public static func image(precision: Float) -> Snapshotting { - return .init( - pathExtension: "png", - diffing: .image(precision: precision) - ) - } -} - -private func NSImagePNGRepresentation(_ image: NSImage) -> Data? { - guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return nil } - let rep = NSBitmapImageRep(cgImage: cgImage) - rep.size = image.size - return rep.representation(using: .png, properties: [:]) -} - -private func compare(_ old: NSImage, _ new: NSImage, precision: Float) -> Bool { - guard let oldCgImage = old.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false } - guard let newCgImage = new.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false } - guard oldCgImage.width != 0 else { return false } - guard newCgImage.width != 0 else { return false } - guard oldCgImage.width == newCgImage.width else { return false } - guard oldCgImage.height != 0 else { return false } - guard newCgImage.height != 0 else { return false } - guard oldCgImage.height == newCgImage.height else { return false } - guard let oldContext = context(for: oldCgImage) else { return false } - guard let newContext = context(for: newCgImage) else { return false } - guard let oldData = oldContext.data else { return false } - guard let newData = newContext.data else { return false } - let byteCount = oldContext.height * oldContext.bytesPerRow - if memcmp(oldData, newData, byteCount) == 0 { return true } - let newer = NSImage(data: NSImagePNGRepresentation(new)!)! - guard let newerCgImage = newer.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false } - guard let newerContext = context(for: newerCgImage) else { return false } - guard let newerData = newerContext.data else { return false } - if memcmp(oldData, newerData, byteCount) == 0 { return true } - if precision >= 1 { return false } - let oldRep = NSBitmapImageRep(cgImage: oldCgImage) - let newRep = NSBitmapImageRep(cgImage: newerCgImage) - var differentPixelCount = 0 - let pixelCount = oldRep.pixelsWide * oldRep.pixelsHigh - let threshold = (1 - precision) * Float(pixelCount) - let p1: UnsafeMutablePointer = oldRep.bitmapData! - let p2: UnsafeMutablePointer = newRep.bitmapData! - for offset in 0 ..< pixelCount * 4 { - if p1[offset] != p2[offset] { - differentPixelCount += 1 - } - if Float(differentPixelCount) > threshold { return false } - } - return true -} - -private func context(for cgImage: CGImage) -> CGContext? { - guard - let space = cgImage.colorSpace, - let context = CGContext( - data: nil, - width: cgImage.width, - height: cgImage.height, - bitsPerComponent: cgImage.bitsPerComponent, - bytesPerRow: cgImage.bytesPerRow, - space: space, - bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue - ) - else { return nil } - - context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height)) - return context -} - -private func diff(_ old: NSImage, _ new: NSImage) -> NSImage { - let oldCiImage = CIImage(cgImage: old.cgImage(forProposedRect: nil, context: nil, hints: nil)!) - let newCiImage = CIImage(cgImage: new.cgImage(forProposedRect: nil, context: nil, hints: nil)!) - let differenceFilter = CIFilter(name: "CIDifferenceBlendMode")! - differenceFilter.setValue(oldCiImage, forKey: kCIInputImageKey) - differenceFilter.setValue(newCiImage, forKey: kCIInputBackgroundImageKey) - let maxSize = CGSize( - width: max(old.size.width, new.size.width), - height: max(old.size.height, new.size.height) - ) - let rep = NSCIImageRep(ciImage: differenceFilter.outputImage!) - let difference = NSImage(size: maxSize) - difference.addRepresentation(rep) - return difference -} -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSView.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSView.swift deleted file mode 100644 index 292570f..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSView.swift +++ /dev/null @@ -1,48 +0,0 @@ -#if os(macOS) -import Cocoa - -extension Snapshotting where Value == NSView, Format == NSImage { - /// A snapshot strategy for comparing views based on pixel equality. - public static var image: Snapshotting { - return .image() - } - - /// A snapshot strategy for comparing views based on pixel equality. - /// - /// - Parameters: - /// - precision: The percentage of pixels that must match. - /// - size: A view size override. - public static func image(precision: Float = 1, size: CGSize? = nil) -> Snapshotting { - return SimplySnapshotting.image(precision: precision).asyncPullback { view in - let initialSize = view.frame.size - if let size = size { view.frame.size = size } - guard view.frame.width > 0, view.frame.height > 0 else { - fatalError("View not renderable to image at size \(view.frame.size)") - } - return view.snapshot ?? Async { callback in - addImagesForRenderedViews(view).sequence().run { views in - let bitmapRep = view.bitmapImageRepForCachingDisplay(in: view.bounds)! - view.cacheDisplay(in: view.bounds, to: bitmapRep) - let image = NSImage(size: view.bounds.size) - image.addRepresentation(bitmapRep) - callback(image) - views.forEach { $0.removeFromSuperview() } - view.frame.size = initialSize - } - } - } - } -} - -extension Snapshotting where Value == NSView, Format == String { - /// A snapshot strategy for comparing views based on a recursive description of their properties and hierarchies. - public static var recursiveDescription: Snapshotting { - return SimplySnapshotting.lines.pullback { view in - return purgePointers( - view.perform(Selector(("_subtreeDescription"))).retain().takeUnretainedValue() - as! String - ) - } - } -} -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSViewController.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSViewController.swift deleted file mode 100644 index 70d9724..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/NSViewController.swift +++ /dev/null @@ -1,26 +0,0 @@ -#if os(macOS) -import Cocoa - -extension Snapshotting where Value == NSViewController, Format == NSImage { - /// A snapshot strategy for comparing view controller views based on pixel equality. - public static var image: Snapshotting { - return .image() - } - - /// A snapshot strategy for comparing view controller views based on pixel equality. - /// - /// - Parameters: - /// - precision: The percentage of pixels that must match. - /// - size: A view size override. - public static func image(precision: Float = 1, size: CGSize? = nil) -> Snapshotting { - return Snapshotting.image(precision: precision, size: size).pullback { $0.view } - } -} - -extension Snapshotting where Value == NSViewController, Format == String { - /// A snapshot strategy for comparing view controller views based on a recursive description of their properties and hierarchies. - public static var recursiveDescription: Snapshotting { - return Snapshotting.recursiveDescription.pullback { $0.view } - } -} -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SceneKit.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SceneKit.swift deleted file mode 100644 index 86dc7ff..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SceneKit.swift +++ /dev/null @@ -1,42 +0,0 @@ -#if os(iOS) || os(macOS) || os(tvOS) -import SceneKit -#if os(macOS) -import Cocoa -#elseif os(iOS) || os(tvOS) -import UIKit -#endif - -#if os(macOS) -extension Snapshotting where Value == SCNScene, Format == NSImage { - /// A snapshot strategy for comparing SceneKit scenes based on pixel equality. - /// - /// - Parameters: - /// - precision: The percentage of pixels that must match. - /// - size: The size of the scene. - public static func image(precision: Float = 1, size: CGSize) -> Snapshotting { - return .scnScene(precision: precision, size: size) - } -} -#elseif os(iOS) || os(tvOS) -extension Snapshotting where Value == SCNScene, Format == UIImage { - /// A snapshot strategy for comparing SceneKit scenes based on pixel equality. - /// - /// - Parameters: - /// - precision: The percentage of pixels that must match. - /// - size: The size of the scene. - public static func image(precision: Float = 1, size: CGSize) -> Snapshotting { - return .scnScene(precision: precision, size: size) - } -} -#endif - -fileprivate extension Snapshotting where Value == SCNScene, Format == Image { - static func scnScene(precision: Float, size: CGSize) -> Snapshotting { - return Snapshotting.image(precision: precision).pullback { scene in - let view = SCNView(frame: .init(x: 0, y: 0, width: size.width, height: size.height)) - view.scene = scene - return view - } - } -} -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SpriteKit.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SpriteKit.swift deleted file mode 100644 index 8d71ce1..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SpriteKit.swift +++ /dev/null @@ -1,42 +0,0 @@ -#if os(iOS) || os(macOS) || os(tvOS) -import SpriteKit -#if os(macOS) -import Cocoa -#elseif os(iOS) || os(tvOS) -import UIKit -#endif - -#if os(macOS) -extension Snapshotting where Value == SKScene, Format == NSImage { - /// A snapshot strategy for comparing SpriteKit scenes based on pixel equality. - /// - /// - Parameters: - /// - precision: The percentage of pixels that must match. - /// - size: The size of the scene. - public static func image(precision: Float = 1, size: CGSize) -> Snapshotting { - return .skScene(precision: precision, size: size) - } -} -#elseif os(iOS) || os(tvOS) -extension Snapshotting where Value == SKScene, Format == UIImage { - /// A snapshot strategy for comparing SpriteKit scenes based on pixel equality. - /// - /// - Parameters: - /// - precision: The percentage of pixels that must match. - /// - size: The size of the scene. - public static func image(precision: Float = 1, size: CGSize) -> Snapshotting { - return .skScene(precision: precision, size: size) - } -} -#endif - -fileprivate extension Snapshotting where Value == SKScene, Format == Image { - static func skScene(precision: Float, size: CGSize) -> Snapshotting { - return Snapshotting.image(precision: precision).pullback { scene in - let view = SKView(frame: .init(x: 0, y: 0, width: size.width, height: size.height)) - view.presentScene(scene) - return view - } - } -} -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/String.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/String.swift deleted file mode 100644 index 3de02fe..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/String.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation -import XCTest - -extension Snapshotting where Value == String, Format == String { - /// A snapshot strategy for comparing strings based on equality. - public static let lines = Snapshotting(pathExtension: "txt", diffing: .lines) -} - -extension Diffing where Value == String { - /// A line-diffing strategy for UTF-8 text. - public static let lines = Diffing( - toData: { Data($0.utf8) }, - fromData: { String(decoding: $0, as: UTF8.self) } - ) { old, new in - guard old != new else { return nil } - let hunks = chunk(diff: SnapshotTesting.diff( - old.split(separator: "\n", omittingEmptySubsequences: false).map(String.init), - new.split(separator: "\n", omittingEmptySubsequences: false).map(String.init) - )) - let failure = hunks - .flatMap { [$0.patchMark] + $0.lines } - .joined(separator: "\n") - let attachment = XCTAttachment(data: Data(failure.utf8), uniformTypeIdentifier: "public.patch-file") - return (failure, [attachment]) - } -} diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SwiftUIView.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SwiftUIView.swift deleted file mode 100644 index ec7bd91..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/SwiftUIView.swift +++ /dev/null @@ -1,83 +0,0 @@ -#if canImport(SwiftUI) -import Foundation -import SwiftUI - -/// The size constraint for a snapshot (similar to `PreviewLayout`). -public enum SwiftUISnapshotLayout { - #if os(iOS) || os(tvOS) - /// Center the view in a device container described by`config`. - case device(config: ViewImageConfig) - #endif - /// Center the view in a fixed size container. - case fixed(width: CGFloat, height: CGFloat) - /// Fit the view to the ideal size that fits its content. - case sizeThatFits -} - -#if os(iOS) || os(tvOS) -@available(iOS 13.0, tvOS 13.0, *) -extension Snapshotting where Value: SwiftUI.View, Format == UIImage { - - /// A snapshot strategy for comparing SwiftUI Views based on pixel equality. - public static var image: Snapshotting { - return .image() - } - - /// A snapshot strategy for comparing SwiftUI Views based on pixel equality. - /// - /// - Parameters: - /// - 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. - /// - precision: The percentage of pixels that must match. - /// - size: A view size override. - /// - traits: A trait collection override. - public static func image( - drawHierarchyInKeyWindow: Bool = false, - precision: Float = 1, - layout: SwiftUISnapshotLayout = .sizeThatFits, - traits: UITraitCollection = .init() - ) - -> Snapshotting { - let config: ViewImageConfig - - switch layout { - #if os(iOS) || os(tvOS) - case let .device(config: deviceConfig): - config = deviceConfig - #endif - case .sizeThatFits: - config = .init(safeArea: .zero, size: nil, traits: traits) - case let .fixed(width: width, height: height): - let size = CGSize(width: width, height: height) - config = .init(safeArea: .zero, size: size, traits: traits) - } - - return SimplySnapshotting.image(precision: precision, scale: traits.displayScale).asyncPullback { view in - var config = config - - let controller: UIViewController - - if config.size != nil { - controller = UIHostingController.init( - rootView: view - ) - } else { - let hostingController = UIHostingController.init(rootView: view) - - let maxSize = CGSize(width: 0.0, height: 0.0) - config.size = hostingController.sizeThatFits(in: maxSize) - - controller = hostingController - } - - return snapshotView( - config: config, - drawHierarchyInKeyWindow: drawHierarchyInKeyWindow, - traits: traits, - view: controller.view, - viewController: controller - ) - } - } -} -#endif -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIBezierPath.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIBezierPath.swift deleted file mode 100644 index 826044f..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIBezierPath.swift +++ /dev/null @@ -1,46 +0,0 @@ -#if os(iOS) || os(tvOS) -import UIKit - -extension Snapshotting where Value == UIBezierPath, Format == UIImage { - /// A snapshot strategy for comparing bezier paths based on pixel equality. - public static var image: Snapshotting { - return .image() - } - - /// A snapshot strategy for comparing bezier paths based on pixel equality. - /// - /// - Parameter precision: The percentage of pixels that must match. - public static func image(precision: Float = 1, scale: CGFloat = 1) -> Snapshotting { - return SimplySnapshotting.image(precision: precision, scale: scale).pullback { path in - let bounds = path.bounds - let format: UIGraphicsImageRendererFormat - if #available(iOS 11.0, tvOS 11.0, *) { - format = UIGraphicsImageRendererFormat.preferred() - } else { - format = UIGraphicsImageRendererFormat.default() - } - format.scale = scale - return UIGraphicsImageRenderer(bounds: bounds, format: format).image { ctx in - path.fill() - } - } - } -} - -@available(iOS 11.0, tvOS 11.0, *) -extension Snapshotting where Value == UIBezierPath, Format == String { - /// A snapshot strategy for comparing bezier paths based on pixel equality. - public static var elementsDescription: Snapshotting { - Snapshotting.elementsDescription.pullback { path in path.cgPath } - } - - /// A snapshot strategy for comparing bezier paths based on pixel equality. - /// - /// - Parameter numberFormatter: The number formatter used for formatting points. - public static func elementsDescription(numberFormatter: NumberFormatter) -> Snapshotting { - Snapshotting.elementsDescription( - numberFormatter: numberFormatter - ).pullback { path in path.cgPath } - } -} -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIImage.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIImage.swift deleted file mode 100644 index bf87a1c..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIImage.swift +++ /dev/null @@ -1,140 +0,0 @@ -#if os(iOS) || os(tvOS) -import UIKit -import XCTest - -extension Diffing where Value == UIImage { - /// A pixel-diffing strategy for UIImage's which requires a 100% match. - public static let image = Diffing.image(precision: 1, scale: nil) - - /// A pixel-diffing strategy for UIImage that allows customizing how precise the matching must be. - /// - /// - Parameter precision: A value between 0 and 1, where 1 means the images must match 100% of their pixels. - /// - Parameter scale: Scale to use when loading the reference image from disk. If `nil` or the `UITraitCollection`s default value of `0.0`, the screens scale is used. - /// - Returns: A new diffing strategy. - public static func image(precision: Float, scale: CGFloat?) -> Diffing { - let imageScale: CGFloat - if let scale = scale, scale != 0.0 { - imageScale = scale - } else { - imageScale = UIScreen.main.scale - } - - return Diffing( - toData: { $0.pngData() ?? emptyImage().pngData()! }, - fromData: { UIImage(data: $0, scale: imageScale)! } - ) { old, new in - guard !compare(old, new, precision: precision) else { return nil } - let difference = SnapshotTesting.diff(old, new) - let message = new.size == old.size - ? "Newly-taken snapshot does not match reference." - : "Newly-taken snapshot@\(new.size) does not match reference@\(old.size)." - let oldAttachment = XCTAttachment(image: old) - oldAttachment.name = "reference" - let newAttachment = XCTAttachment(image: new) - newAttachment.name = "failure" - let differenceAttachment = XCTAttachment(image: difference) - differenceAttachment.name = "difference" - return ( - message, - [oldAttachment, newAttachment, differenceAttachment] - ) - } - } - - - /// Used when the image size has no width or no height to generated the default empty image - private static func emptyImage() -> UIImage { - let label = UILabel(frame: CGRect(x: 0, y: 0, width: 400, height: 80)) - label.backgroundColor = .red - label.text = "Error: No image could be generated for this view as its size was zero. Please set an explicit size in the test." - label.textAlignment = .center - label.numberOfLines = 3 - return label.asImage() - } -} - -extension Snapshotting where Value == UIImage, Format == UIImage { - /// A snapshot strategy for comparing images based on pixel equality. - public static var image: Snapshotting { - return .image(precision: 1, scale: nil) - } - - /// A snapshot strategy for comparing images based on pixel equality. - /// - /// - Parameter precision: The percentage of pixels that must match. - /// - Parameter scale: The scale of the reference image stored on disk. - public static func image(precision: Float, scale: CGFloat?) -> Snapshotting { - return .init( - pathExtension: "png", - diffing: .image(precision: precision, scale: scale) - ) - } -} - -private func compare(_ old: UIImage, _ new: UIImage, precision: Float) -> Bool { - guard let oldCgImage = old.cgImage else { return false } - guard let newCgImage = new.cgImage else { return false } - guard oldCgImage.width != 0 else { return false } - guard newCgImage.width != 0 else { return false } - guard oldCgImage.width == newCgImage.width else { return false } - guard oldCgImage.height != 0 else { return false } - guard newCgImage.height != 0 else { return false } - guard oldCgImage.height == newCgImage.height else { return false } - // Values between images may differ due to padding to multiple of 64 bytes per row, - // because of that a freshly taken view snapshot may differ from one stored as PNG. - // At this point we're sure that size of both images is the same, so we can go with minimal `bytesPerRow` value - // and use it to create contexts. - let minBytesPerRow = min(oldCgImage.bytesPerRow, newCgImage.bytesPerRow) - let byteCount = minBytesPerRow * oldCgImage.height - - var oldBytes = [UInt8](repeating: 0, count: byteCount) - guard let oldContext = context(for: oldCgImage, bytesPerRow: minBytesPerRow, data: &oldBytes) else { return false } - guard let oldData = oldContext.data else { return false } - if let newContext = context(for: newCgImage, bytesPerRow: minBytesPerRow), let newData = newContext.data { - if memcmp(oldData, newData, byteCount) == 0 { return true } - } - let newer = UIImage(data: new.pngData()!)! - guard let newerCgImage = newer.cgImage else { return false } - var newerBytes = [UInt8](repeating: 0, count: byteCount) - guard let newerContext = context(for: newerCgImage, bytesPerRow: minBytesPerRow, data: &newerBytes) else { return false } - guard let newerData = newerContext.data else { return false } - if memcmp(oldData, newerData, byteCount) == 0 { return true } - if precision >= 1 { return false } - var differentPixelCount = 0 - let threshold = 1 - precision - for byte in 0.. threshold { return false} - } - return true -} - -private func context(for cgImage: CGImage, bytesPerRow: Int, data: UnsafeMutableRawPointer? = nil) -> CGContext? { - guard - let space = cgImage.colorSpace, - let context = CGContext( - data: data, - width: cgImage.width, - height: cgImage.height, - bitsPerComponent: cgImage.bitsPerComponent, - bytesPerRow: bytesPerRow, - space: space, - bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue - ) - else { return nil } - - context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height)) - return context -} - -private func diff(_ old: UIImage, _ new: UIImage) -> UIImage { - let width = max(old.size.width, new.size.width) - let height = max(old.size.height, new.size.height) - UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), true, 0) - new.draw(at: .zero) - old.draw(at: .zero, blendMode: .difference, alpha: 1) - let differenceImage = UIGraphicsGetImageFromCurrentImageContext()! - UIGraphicsEndImageContext() - return differenceImage -} -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIView.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIView.swift deleted file mode 100644 index fe1e81a..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIView.swift +++ /dev/null @@ -1,65 +0,0 @@ -#if os(iOS) || os(tvOS) -import UIKit - -extension Snapshotting where Value == UIView, Format == UIImage { - /// A snapshot strategy for comparing views based on pixel equality. - public static var image: Snapshotting { - return .image() - } - - /// A snapshot strategy for comparing views based on pixel equality. - /// - /// - Parameters: - /// - 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. - /// - precision: The percentage of pixels that must match. - /// - size: A view size override. - /// - traits: A trait collection override. - public static func image( - drawHierarchyInKeyWindow: Bool = false, - precision: Float = 1, - size: CGSize? = nil, - traits: UITraitCollection = .init() - ) - -> Snapshotting { - - return SimplySnapshotting.image(precision: precision, scale: traits.displayScale).asyncPullback { view in - snapshotView( - config: .init(safeArea: .zero, size: size ?? view.frame.size, traits: .init()), - drawHierarchyInKeyWindow: drawHierarchyInKeyWindow, - traits: traits, - view: view, - viewController: .init() - ) - } - } -} - -extension Snapshotting where Value == UIView, Format == String { - /// A snapshot strategy for comparing views based on a recursive description of their properties and hierarchies. - public static var recursiveDescription: Snapshotting { - return Snapshotting.recursiveDescription() - } - - /// A snapshot strategy for comparing views based on a recursive description of their properties and hierarchies. - public static func recursiveDescription( - size: CGSize? = nil, - traits: UITraitCollection = .init() - ) - -> Snapshotting { - return SimplySnapshotting.lines.pullback { view in - let dispose = prepareView( - config: .init(safeArea: .zero, size: size ?? view.frame.size, traits: traits), - drawHierarchyInKeyWindow: false, - traits: .init(), - view: view, - viewController: .init() - ) - defer { dispose() } - return purgePointers( - view.perform(Selector(("recursiveDescription"))).retain().takeUnretainedValue() - as! String - ) - } - } -} -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIViewController.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIViewController.swift deleted file mode 100644 index 45e719c..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/UIViewController.swift +++ /dev/null @@ -1,114 +0,0 @@ -#if os(iOS) || os(tvOS) -import UIKit - -extension Snapshotting where Value == UIViewController, Format == UIImage { - /// A snapshot strategy for comparing view controller views based on pixel equality. - public static var image: Snapshotting { - return .image() - } - - /// A snapshot strategy for comparing view controller views based on pixel equality. - /// - /// - Parameters: - /// - config: A set of device configuration settings. - /// - precision: The percentage of pixels that must match. - /// - size: A view size override. - /// - traits: A trait collection override. - public static func image( - on config: ViewImageConfig, - precision: Float = 1, - size: CGSize? = nil, - traits: UITraitCollection = .init() - ) - -> Snapshotting { - - return SimplySnapshotting.image(precision: precision, scale: traits.displayScale).asyncPullback { viewController in - snapshotView( - config: size.map { .init(safeArea: config.safeArea, size: $0, traits: config.traits) } ?? config, - drawHierarchyInKeyWindow: false, - traits: traits, - view: viewController.view, - viewController: viewController - ) - } - } - - /// A snapshot strategy for comparing view controller views based on pixel equality. - /// - /// - Parameters: - /// - 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. - /// - precision: The percentage of pixels that must match. - /// - size: A view size override. - /// - traits: A trait collection override. - public static func image( - drawHierarchyInKeyWindow: Bool = false, - precision: Float = 1, - size: CGSize? = nil, - traits: UITraitCollection = .init() - ) - -> Snapshotting { - - return SimplySnapshotting.image(precision: precision, scale: traits.displayScale).asyncPullback { viewController in - snapshotView( - config: .init(safeArea: .zero, size: size, traits: traits), - drawHierarchyInKeyWindow: drawHierarchyInKeyWindow, - traits: .init(), - view: viewController.view, - viewController: viewController - ) - } - } -} - -extension Snapshotting where Value == UIViewController, Format == String { - /// A snapshot strategy for comparing view controllers based on their embedded controller hierarchy. - public static var hierarchy: Snapshotting { - return Snapshotting.lines.pullback { viewController in - let dispose = prepareView( - config: .init(), - drawHierarchyInKeyWindow: false, - traits: .init(), - view: viewController.view, - viewController: viewController - ) - defer { dispose() } - return purgePointers( - viewController.perform(Selector(("_printHierarchy"))).retain().takeUnretainedValue() as! String - ) - } - } - - /// A snapshot strategy for comparing view controller views based on a recursive description of their properties and hierarchies. - public static var recursiveDescription: Snapshotting { - return Snapshotting.recursiveDescription() - } - - /// A snapshot strategy for comparing view controller views based on a recursive description of their properties and hierarchies. - /// - /// - Parameters: - /// - config: A set of device configuration settings. - /// - size: A view size override. - /// - traits: A trait collection override. - public static func recursiveDescription( - on config: ViewImageConfig = .init(), - size: CGSize? = nil, - traits: UITraitCollection = .init() - ) - -> Snapshotting { - return SimplySnapshotting.lines.pullback { viewController in - let dispose = prepareView( - config: .init(safeArea: config.safeArea, size: size ?? config.size, traits: config.traits), - drawHierarchyInKeyWindow: false, - traits: traits, - view: viewController.view, - viewController: viewController - ) - defer { dispose() } - return purgePointers( - viewController.view.perform(Selector(("recursiveDescription"))).retain().takeUnretainedValue() - as! String - ) - } - } -} -#endif diff --git a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/URLRequest.swift b/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/URLRequest.swift deleted file mode 100644 index 10ea42e..0000000 --- a/Example/Pods/SnapshotTesting/Sources/SnapshotTesting/Snapshotting/URLRequest.swift +++ /dev/null @@ -1,83 +0,0 @@ -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -extension Snapshotting where Value == URLRequest, Format == String { - /// A snapshot strategy for comparing requests based on raw equality. - public static let raw = Snapshotting.raw(pretty: false) - - /// A snapshot strategy for comparing requests based on raw equality. - /// - /// - Parameter pretty: Attempts to pretty print the body of the request (supports JSON). - public static func raw(pretty: Bool) -> Snapshotting { - return SimplySnapshotting.lines.pullback { (request: URLRequest) in - let method = "\(request.httpMethod ?? "GET") \(request.url?.absoluteString ?? "(null)")" - - let headers = (request.allHTTPHeaderFields ?? [:]) - .map { key, value in "\(key): \(value)" } - .sorted() - - let body: [String] - do { - if pretty, #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) { - body = try request.httpBody - .map { try JSONSerialization.jsonObject(with: $0, options: []) } - .map { try JSONSerialization.data(withJSONObject: $0, options: [.prettyPrinted, .sortedKeys]) } - .map { ["\n\(String(decoding: $0, as: UTF8.self))"] } - ?? [] - } else { - throw NSError(domain: "co.pointfree.Never", code: 1, userInfo: nil) - } - } - catch { - body = request.httpBody - .map { ["\n\(String(decoding: $0, as: UTF8.self))"] } - ?? [] - } - - return ([method] + headers + body).joined(separator: "\n") - } - } - - /// A snapshot strategy for comparing requests based on a cURL representation. - public static let curl = SimplySnapshotting.lines.pullback { (request: URLRequest) in - - var components = ["curl"] - - // HTTP Method - let httpMethod = request.httpMethod! - switch httpMethod { - case "GET": break - case "HEAD": components.append("--head") - default: components.append("--request \(httpMethod)") - } - - // Headers - if let headers = request.allHTTPHeaderFields { - for field in headers.keys.sorted() where field != "Cookie" { - let escapedValue = headers[field]!.replacingOccurrences(of: "\"", with: "\\\"") - components.append("--header \"\(field): \(escapedValue)\"") - } - } - - // Body - if let httpBodyData = request.httpBody, let httpBody = String(data: httpBodyData, encoding: .utf8) { - var escapedBody = httpBody.replacingOccurrences(of: "\\\"", with: "\\\\\"") - escapedBody = escapedBody.replacingOccurrences(of: "\"", with: "\\\"") - - components.append("--data \"\(escapedBody)\"") - } - - // Cookies - if let cookie = request.allHTTPHeaderFields?["Cookie"] { - let escapedValue = cookie.replacingOccurrences(of: "\"", with: "\\\"") - components.append("--cookie \"\(escapedValue)\"") - } - - // URL - components.append("\"\(request.url!.absoluteString)\"") - - return components.joined(separator: " \\\n\t") - } -} diff --git a/Example/Pods/SwiftConfigurationFiles/.swiftformat b/Example/Pods/SwiftConfigurationFiles/.swiftformat deleted file mode 100644 index 6e0c1f3..0000000 --- a/Example/Pods/SwiftConfigurationFiles/.swiftformat +++ /dev/null @@ -1,33 +0,0 @@ -# -# .swiftformat -# Created by Felix Mau (https://felix.hamburg) -# - -# -# Configuration file for SwiftFormat (https://github.com/nicklockwood/SwiftFormat/) -# -# A more detailed documentation of the rules can be found at -# https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md -# - -# -# File options -# -# Note: Excluded paths are relative to the SwiftFormat configuration file. -# Therefore we can't specify them in the here. Just leaving this commented out line here as a reminder. -# -# --exclude Pods,Generated - -# -# Rules -# ---disable andOperator ---disable blankLinesAtStartOfScope ---disable wrapMultilineStatementBraces - -# -# Rule Configuration -# ---wraparguments "after-first" ---wrapcollections "before-first" ---importgrouping testable-last \ No newline at end of file diff --git a/Example/Pods/SwiftConfigurationFiles/.swiftlint.yml b/Example/Pods/SwiftConfigurationFiles/.swiftlint.yml deleted file mode 100644 index 49222d7..0000000 --- a/Example/Pods/SwiftConfigurationFiles/.swiftlint.yml +++ /dev/null @@ -1,93 +0,0 @@ -# -# .swiftlint.yml -# -# This file enables [SwiftLint](https://github.com/realm/SwiftLint) rules, that are disabled by default. -# -# Created by Felix Mau (https://felix.hamburg) -# -# Based on: -# - https://gist.github.com/tweetjay/1b7f00b6f312a5a2cf7b5676420b9b0c -# - https://github.com/brandenr/swiftlintconfig -# - https://www.avanderlee.com/optimization/swiftlint-optin-rules/ -# - -opt_in_rules: - - anyobject_protocol # Prefer using `AnyObject` over class for class-only protocols. - - array_init # Prefer using `Array(seq)` over `seq.map { $0 }` to convert a sequence into an Array. - - balanced_xctest_lifecycle # Test classes must implement balanced setUp and tearDown methods. - - block_based_kvo # Prefer the new block based KVO API with keypaths when using Swift 3.2 or later. - - closure_spacing # Closure expressions should have a single space inside each brace. - - contains_over_filter_count # Prefer `contains` over comparing `filter(where:).count` to 0. - - contains_over_filter_is_empty # Prefer `contains` over using `filter(where:).isEmpty`. - - contains_over_first_not_nil # Prefer `contains` over `first(where:) != nil` and `firstIndex(where:) != nil`. - - convenience_type # Types used for hosting only static members should be implemented as a caseless enum to avoid instantiation. - - empty_collection_literal # Prefer checking `isEmpty` over comparing collection to an empty array or dictionary literal. - - empty_count # Prefer checking `isEmpty` over comparing count to zero. - - empty_parameters # Prefer `() ->` over `Void ->`. - - empty_string # Prefer checking `isEmpty` over comparing string to an empty string literal. - - empty_xctest_method # Empty XCTest method should be avoided. - - explicit_init # Explicitly calling .init() should be avoided. - - fallthrough # Fallthrough should be avoided. - - fatal_error_message # A fatalError call should have a message. - - file_header # Header comments should be consistent with project patterns. - - first_where # Prefer using `.first(where:)` over `.filter { }.first` in collections. - - for_where # `where` clauses are preferred over a single `if` inside a `for`. - - force_unwrapping # Force unwrapping should be avoided. - - identical_operands # Comparing two identical operands is likely a mistake. - - is_disjoint # Prefer using `Set.isDisjoint(with:)` over `Set.intersection(_:).isEmpty`. - - last_where # Prefer using `.last(where:)` over `.filter { }.last` in collections. - - legacy_multiple # Prefer using the `isMultiple(of:)` function instead of using the remainder operator (`%`). - - legacy_random # Prefer using `type.random(in:)` over legacy functions, e.g. `arc4random()`. - - lower_acl_than_parent # Ensure definitions have a lower access control level than their enclosing parent. - - missing_docs # Declarations should be documented. - - modifier_order # Modifier order should be consistent. - - object_literal # Prefer object literals over image and color inits. - - operator_usage_whitespace # Operators should be surrounded by a single whitespace when they are being used. - - optional_enum_case_matching # Matching an enum case against an optional enum without ‘?’. - - overridden_super_call # Some overridden methods should always call super - - prefer_self_type_over_type_of_self # Prefer `Self` over `type(of: self)` when accessing properties or calling methods. - - prefer_zero_over_explicit_init # Prefer `.zero` over explicit init with zero parameters (e.g. `CGPoint(x: 0, y: 0)`). - - private_subject # Combine Subject should be private. - - private_outlet # IBOutlets should be private to avoid leaking UIKit to higher layers. - - private_action # IBActions should be private. - - prohibited_super_call # Some methods should not call super, e.g. `UIViewController.loadView()` - - sorted_first_last # Prefer using `min()` or `max()` over `sorted().first` or `sorted().last` - - toggle_bool # Prefer `someBool.toggle()` over `someBool = !someBool`. - - unavailable_function # Unimplemented functions should be marked as unavailable. - - unused_import # All imported modules should be required to make the file compile. - - unused_optional_binding # Prefer `!= nil` over `let _ =` - - unused_declaration # Declarations should be referenced at least once within all files linted. - - void_return # Prefer `-> Void` over `-> ()`. - - -file_header: - required_pattern: | - \/\/ - \/\/ SWIFTLINT_CURRENT_FILENAME - -line_length: 160 - -trailing_comma: - mandatory_comma: true - -custom_rules: - comments_space: - name: 'Space After Comment' - regex: '(^ *//\w+)' - message: 'There should be a space after //.' - severity: warning - empty_line_after_guard: - name: 'Empty Line After Guard' - regex: '(^ *guard[ a-zA-Z0-9=?.\(\),> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/Example/Pods/SwiftConfigurationFiles/README.md b/Example/Pods/SwiftConfigurationFiles/README.md deleted file mode 100644 index 1812f97..0000000 --- a/Example/Pods/SwiftConfigurationFiles/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Swift Configuration Files 🛠 -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). - -### Author -Felix Mau (me(@)felix.hamburg) - -### License - -Swift Configuration Files is available under the MIT license. See the LICENSE file for more info. \ No newline at end of file diff --git a/Example/Pods/SwiftFormat/CommandLineTool/swiftformat b/Example/Pods/SwiftFormat/CommandLineTool/swiftformat deleted file mode 100755 index 61b7b14..0000000 Binary files a/Example/Pods/SwiftFormat/CommandLineTool/swiftformat and /dev/null differ diff --git a/Example/Pods/SwiftFormat/LICENSE.md b/Example/Pods/SwiftFormat/LICENSE.md deleted file mode 100755 index 6fc7a2c..0000000 --- a/Example/Pods/SwiftFormat/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 Nick Lockwood - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Example/Pods/SwiftFormat/README.md b/Example/Pods/SwiftFormat/README.md deleted file mode 100644 index aff6190..0000000 --- a/Example/Pods/SwiftFormat/README.md +++ /dev/null @@ -1,886 +0,0 @@ -![](EditorExtension/Application/Assets.xcassets/AppIcon.appiconset/icon_256x256.png) - -[![PayPal](https://img.shields.io/badge/paypal-donate-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9ZGWNK5FEZFF6&source=url) -[![Build](https://github.com/nicklockwood/SwiftFormat/actions/workflows/build.yml/badge.svg)](https://github.com/nicklockwood/SwiftFormat/actions/workflows/build.yml) -[![Codecov](https://codecov.io/gh/nicklockwood/SwiftFormat/graphs/badge.svg)](https://codecov.io/gh/nicklockwood/SwiftFormat) -[![Swift 4.2](https://img.shields.io/badge/swift-4.2-red.svg?style=flat)](https://developer.apple.com/swift) -[![License](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://opensource.org/licenses/MIT) -[![Twitter](https://img.shields.io/badge/twitter-@nicklockwood-blue.svg)](http://twitter.com/nicklockwood) - -Table of Contents ------------------ - -- [What?](#what-is-this) -- [Why?](#why-would-i-want-to-do-that) -- [How?](#how-do-i-install-it) - - [Command-line tool](#command-line-tool) - - [Xcode source editor extension](#xcode-source-editor-extension) - - [Xcode build phase](#xcode-build-phase) - - [Via Applescript](#via-applescript) - - [VSCode plugin](#vscode-plugin) - - [Sublime Text plugin](#sublime-text-plugin) - - [Git pre-commit hook](#git-pre-commit-hook) - - [On CI using Danger](#on-ci-using-danger) - - [Bazel build](#bazel-build) - - [Docker](#docker) -- [Configuration](#configuration) - - [Options](#options) - - [Rules](#rules) - - [Swift version](#swift-version) - - [Config file](#config-file) - - [Globs](#globs) - - [Linting](#linting) - - [Error codes](#error-codes) - - [Cache](#cache) - - [File headers](#file-headers) -- [FAQ](#faq) -- [Known issues](#known-issues) -- [Tip Jar](#tip-jar) -- [Credits](#credits) - - -What is this? ----------------- - -SwiftFormat is a code library and command-line tool for reformatting Swift code on macOS or Linux. - -SwiftFormat goes above and beyond what you might expect from a code formatter. In addition to adjusting white space it can insert or remove implicit `self`, remove redundant parentheses, and correct many other deviations from the standard Swift idioms. - - -Why would I want to do that? ------------------------------ - -Many programmers have a preferred style for formatting their code, and others seem entirely blind to the existing formatting conventions of a project (to the enragement of their colleagues). - -When collaborating on a project, it can be helpful to agree on a common coding style, but enforcing that manually is tedious and error-prone, and can lead to arguments if some participants take it more seriously than others. - -Having a tool to automatically enforce a common style eliminates those issues, and lets you focus on the behavior of the code, not its presentation. - - -How do I install it? ---------------------- - -That depends - There are several ways you can use SwiftFormat: - -1. As a command-line tool that you run manually, or as part of some other toolchain -2. As a Source Editor Extension that you can invoke via the Editor > SwiftFormat menu within Xcode -3. As a build phase in your Xcode project, so that it runs every time you press Cmd-R or Cmd-B, or -4. As a Git pre-commit hook, so that it runs on any files you've changed before you check them in - - -Command-line tool -------------------- - -**NOTE:** if you are using any of the following methods to install SwiftFormat on macOS 10.14.3 or earlier and are experiencing a crash on launch, you may need to install the [Swift 5 Runtime Support for Command Line Tools](https://support.apple.com/kb/DL1998). See [known issues](#known-issues) for details. - -**Installation:** - -You can install the `swiftformat` command-line tool on macOS or Linux using [Homebrew](http://brew.sh/). Assuming you already have Homebrew installed, just type: - -```bash -$ brew install swiftformat -``` - -To update to the latest version once installed: - -```bash -$ brew upgrade swiftformat -``` - -Alternatively, you can install the tool on macOS or Linux by using [Mint](https://github.com/yonaskolb/Mint) as follows: - -```bash -$ mint install nicklockwood/SwiftFormat -``` - -Or if you prefer, you can check out and build SwiftFormat manually on macOS, Linux or Windows as follows: - -```bash -$ git clone https://github.com/nicklockwood/SwiftFormat -$ cd SwiftFormat -$ swift build -c release -``` - -If you are installing SwiftFormat into your project directory, you can use [CocoaPods](https://cocoapods.org/) on macOS to automatically install the swiftformat binary along with your other pods - see the Xcode build phase instructions below for details. - -Another option is to include the binary artifactbundle in your `Package.swift`: - -```swift -.binaryTarget( - name: "SwiftFormat", - url: "https://github.com/nicklockwood/SwiftFormat/releases/download/0.49.12/swiftformat-macos.artifactbundle.zip", - checksum: "CHECKSUM" -), -``` - -If you would prefer not to use a package manager, you can build the command-line app manually: - -1. open `SwiftFormat.xcodeproj` and build the `SwiftFormat (Application)` scheme. - -2. Drag the `swiftformat` binary into `/usr/local/bin/` (this is a hidden folder, but you can use the Finder's `Go > Go to Folder...` menu to open it). - -3. Open `~/.bash_profile` in your favorite text editor (this is a hidden file, but you can type `open ~/.bash_profile` in the terminal to open it). - -4. Add the following line to the file: `alias swiftformat="/usr/local/bin/swiftformat --indent 4"` (you can omit the `--indent 4`, or replace it with something else. Run `swiftformat --help` to see the available options). - -5. Save the `.bash_profile` file and run the command `source ~/.bash_profile` for the changes to take effect. - -**Usage:** - -If you followed the installation instructions above, you can now just type - -```bash -$ swiftformat . -``` - -(that's a space and then a period after the command) in the terminal to format any Swift files in the current directory. In place of the `.`, you can instead type an absolute or relative path to the file or directory that you want to format. - -**WARNING:** `swiftformat .` will overwrite any Swift files it finds in the current directory, and any subfolders therein. If you run it in your home directory, it will probably reformat every Swift file on your hard drive. - -To use it safely, do the following: - -1. Choose a file or directory that you want to apply the changes to. - -2. Make sure that you have committed all your changes to that code safely in git (or whatever source control system you use). - -3. (Optional) In Terminal, type `swiftformat --inferoptions "/path/to/your/code/"`. This will suggest a set of formatting options to use that match your existing project style (but you are free to ignore these and use the defaults, or your own settings if you prefer). - - The path can point to either a single Swift file or a directory of files. It can be either be absolute, or relative to the current directory. The `""` quotes around the path are optional, but if the path contains spaces then you either need to use quotes, or escape each space with `\`. You may include multiple paths separated by spaces. - -4. In Terminal, type `swiftformat "/path/to/your/code/"`. The same rules apply as above with respect to paths, and multiple space-delimited paths are allowed. - - If you used `--inferoptions` to generate a suggested set of options in step 3, you should copy and paste them into the command, either before or after the path(s) to your source files. - - If you have created a [config file](#config-file), you can specify its path using `--config "/path/to/your/config-file/"`. Alternatively, if you name the file `.swiftformat` and place it inside the project you are formatting, it will be picked up automatically. - -5. Press enter to begin formatting. Once the formatting is complete, use your source control system to check the changes, and verify that no undesirable changes have been introduced. If they have, revert the changes, tweak the options and try again. - -6. (Optional) commit the changes. - -Following these instructions *should* ensure that you avoid catastrophic data loss, but in the unlikely event that it wipes your hard drive, **please note that I accept no responsibility**. - -**Using Standard Input/Output:** - -If you prefer, you can use unix pipes to include SwiftFormat as part of a command chain. For example, this is an alternative way to format a file: - -```bash -$ cat /path/to/file.swift | swiftformat --output /path/to/file.swift -``` - -Omitting the `--output /path/to/file.swift` will print the formatted file to Standard Output (stdout). You can also pass "stdout" explicitly as the output path: - -```bash -$ cat /path/to/file.swift | swiftformat --output stdout -``` - -Or you can use `>` to specify the output path as follows: - -```bash -$ cat /path/to/file.swift | swiftformat > /path/to/file.swift -``` - -If you do not supply an input file, SwiftFormat will automatically take its input from Standard Input (stdin), but will time-out if no input is received immediately and display the help screen. To make it explicit, pass "stdin" as the input path: - -```bash -$ cat /path/to/file.swift | swiftformat stdin -``` - -When using stdin, SwiftFormat does not have access to the file path of the input, so features that rely on the file location (such as inserting the creation date into header comments, or detecting `.swiftformat` configuration files in the file path) will not work. To solve this, you can provide the file path using the `--stdinpath` argument: - -```bash -$ cat /path/to/file.swift | swiftformat stdin --stdinpath /path/to/file.swift -``` - - -Xcode source editor extension ------------------------------ - -**Installation:** - -Like the command-line tool, you can install the SwiftFormat for Xcode extension application via [Homebrew](http://brew.sh/). Assuming you already have Homebrew installed, type: - -```bash -$ brew install --cask swiftformat-for-xcode -``` - -This will install SwiftFormat for Xcode in your Applications folder. Double-click the app to launch it, and then follow the on-screen instructions. - -**NOTE:** The app should be correctly signed, but if you get a Gatekeeper warning when trying to open it you can bypass this by right-clicking (or control-clicking) the app and selecting `Open`. - -To update to the latest version once installed use: - -```bash -$ brew upgrade --cask swiftformat-for-xcode -``` - -Alternatively, if you prefer not to use Homebrew, you'll find the latest version of the SwiftFormat for Xcode application inside the EditorExtension folder included in the SwiftFormat repository. Download and unpack the zip archive, then drag `SwiftFormat for Xcode.app` into your `Applications` folder. - -**Usage:** - -Once you have launched the app and restarted Xcode, you'll find a SwiftFormat option under Xcode's Editor menu. If the SwiftFormat menu does not appear [this thread](https://github.com/nicklockwood/SwiftFormat/issues/494) may help. - -You can configure the formatting [rules](#rules) and [options](#options) using the SwiftFormat for Xcode host application. There is currently no way to override these per-project, however, you can import and export different configurations using the File menu. You will need to do this again each time you switch projects. - -The format of the configuration file is described in the [Config section](#config-file) below. - -**Note:** SwiftFormat for Xcode cannot automatically detect changes to an imported configuration file. If you update the `.swiftformat` file for your project, you will need to manually re-import that file into SwiftFormat for Xcode in order for the Xcode source editor extension to use the new configuration. - - -Xcode build phase -------------------- - -**NOTE:** Adding this script will overwrite your source files as you work on them, which has the annoying side-effect of clearing the undo history. You may wish to add the script to your test target rather than your main target, so that it is invoked only when you run the unit tests, and not every time you build the app. - -Alternatively, you might want to consider running SwiftFormat in [lint](#linting) mode as part of your normal build, and then running a formatting pass manually, or as part of a less-frequent build target (such as the tests). - -### Using Swift Package Manager - -To set up SwiftFormat as an Xcode build phase, do the following: - -#### 1) Create a BuildTools folder and Package.swift - -1. Create a folder called `BuildTools` in the same folder as your xcodeproj file -2. In this folder, create a file called `Package.swift`, with the following contents: -```swift -// swift-tools-version:5.1 -import PackageDescription - -let package = Package( - name: "BuildTools", - platforms: [.macOS(.v10_11)], - dependencies: [ - .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.49.0"), - ], - targets: [.target(name: "BuildTools", path: "")] -) -``` -3. If you are running Xcode 11.4 or later, in the `BuildTools` folder create a file called `Empty.swift` with nothing in it. This is to satisfy a change in Swift Package Manager. - -#### 2) Add a Build phase to your app target - -1. Click on your project in the file list, choose your target under `TARGETS`, click the `Build Phases` tab -2. Add a `New Run Script Phase` by clicking the little plus icon in the top left -3. Drag the new `Run Script` phase **above** the `Compile Sources` phase, expand it and paste the following script: - - ```bash - cd BuildTools - SDKROOT=(xcrun --sdk macosx --show-sdk-path) - #swift package update #Uncomment this line temporarily to update the version used to the latest matching your BuildTools/Package.swift file - swift run -c release swiftformat "$SRCROOT" - ``` - -You can also use `swift run -c release --package-path BuildTools swiftformat "$SRCROOT"` if you need a more complex script and `cd BuildTools` breaks stuff. - -**NOTE:** You may wish to check BuildTools/Package.swift into your source control so that the version used by your run-script phase is kept in version control. It is recommended to add the following to your .gitignore file: `BuildTools/.build` and `BuildTools/.swiftpm`. - -### Using CocoaPods - -#### 1) Add the SwiftFormat CLI to your Podfile - -1. Add the `swiftformat` binary to your project directory via [CocoaPods](https://cocoapods.org/), by adding the following line to your Podfile then running `pod install`: - - ```ruby - pod 'SwiftFormat/CLI', '~> 0.49' - ``` - -**NOTE:** This will only install the pre-built command-line app, not the source code for the SwiftFormat framework. - -**NOTE (2):** When installing this way, GateKeeper may block swiftformat from running until you open it manually the first time by right-clicking in the Finder and selecting "Open". - -#### 2) Add a Build phase to your app target - -1. Click on your project in the file list, choose your target under `TARGETS`, click the `Build Phases` tab -2. Add a `New Run Script Phase` by clicking the little plus icon in the top left -3. Drag the new `Run Script` phase **above** the `Compile Sources` phase, expand it and paste the following script: - - ```bash - "${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat" "$SRCROOT" - ``` - -### Alternative: Locally installed SwiftFormat - -Alternatively, you could use a locally installed swiftformat command-line tool instead by putting the following in your Run Script build phase: - -```bash -if which swiftformat >/dev/null; then - swiftformat . -else - echo "warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat" -fi -``` - -This is not recommended for shared projects however, as different team members using different versions of SwiftFormat may result in noise in the commit history as code gets reformatted inconsistently. - - -Via AppleScript ----------------- - -To run SwiftFormat on the frontmost Xcode document (project or workspace) you can use the following AppleScript: - -```applescript -tell application "Xcode" - set frontWindow to the first window - set myPath to path of document of frontWindow - do shell script "cd " & myPath & ";cd ..; /usr/local/bin/swiftformat ." -end tell -``` - -Some Apps you can trigger this from are [BetterTouchTool](https://folivora.ai), [Alfred](https://www.alfredapp.com) or [Keyboard Maestro](https://www.keyboardmaestro.com/main/). Another option is to define a QuickAction for Xcode via Automator and then assign a keyboard shortcut for it in the System Preferences. - - -VSCode plugin --------------- - -If you prefer to use Microsoft's [VSCode](https://code.visualstudio.com) editor for writing Swift, [Valentin Knabel](https://github.com/vknabel) has created a [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=vknabel.vscode-swiftformat) for SwiftFormat. - - -Sublime Text plugin --------------------- - -If you prefer to use the [Sublime Text](https://www.sublimetext.com) editor, try the [Sublime-Swift-Format plugin](https://github.com/aerobounce/Sublime-Swift-Format) by [Aerobounce](https://github.com/aerobounce). - - -Git pre-commit hook ---------------------- - -1. Follow the instructions for installing the SwiftFormat command-line tool. - -2. Install [git-format-staged](https://github.com/hallettj/git-format-staged). - -3. Edit or create a `.git/hooks/pre-commit` file in your project folder. The .git folder is hidden but should already exist if you are using Git with your project, so open it with the terminal, or the Finder's `Go > Go to Folder...` menu. - -4. Add the following line in the pre-commit file. The `{}` will be replaced automatically by the path to the Swift file being formatted: - - ```bash - #!/bin/bash - git-format-staged --formatter "swiftformat stdin --stdinpath '{}'" "*.swift" - ``` - - (Note that this example uses your locally installed version of SwiftFormat, not a separate copy in your project repository. You can replace `swiftformat` with the path to a copy inside your project if you prefer.) - -5. enable the hook by typing `chmod +x .git/hooks/pre-commit` in the terminal. - -The pre-commit hook will now run whenever you run `git commit`. Running `git commit --no-verify` will skip the pre-commit hook. - -**NOTE:** If you are using Git via a GUI client such as [Tower](https://www.git-tower.com), [additional steps](https://www.git-tower.com/help/mac/faq-and-tips/faq/hook-scripts) may be needed. - -**NOTE (2):** Unlike the Xcode build phase approach, git pre-commit hook won't be checked in to source control, and there's no way to guarantee that all users of the project are using the same version of SwiftFormat. For a collaborative project, you might want to consider a *post*-commit hook instead, which would run on your continuous integration server. - -On CI using Danger -------------------- - -To setup SwiftFormat to be used by your continuous integration system using [Danger](http://danger.systems/ruby/), do the following: - -1. Follow the [`instructions`](http://danger.systems/guides/getting_started.html) to setup Danger. -2. Add the [`danger-swiftformat`](https://github.com/garriguv/danger-ruby-swiftformat) plugin to your `Gemfile`. -3. Add the following to your `Dangerfile`: - - ```ruby - swiftformat.binary_path = "/path/to/swiftformat" # optional - swiftformat.additional_args = "--indent tab --self insert" # optional - swiftformat.check_format(fail_on_error: true) - ``` - - **NOTE:** It is recommended to add the `swiftformat` binary to your project directory to ensure the same version is used each time (see the [Xcode build phase](#xcode-build-phase) instructions above). - - -Bazel Build ------------ - -If you use [Bazel](https://bazel.build/) to build your Swift projects and want to ensure that only properly formatted code is merged to your main branch, try [rules_swiftformat](https://github.com/cgrindel/rules_swiftformat). The repository contains Bazel rules and macros that format Swift source files using SwiftFormat, test that the formatted files exist in the workspace directory, and copy the formatted files to the workspace directory. - - -Docker ------------ - -SwiftFormat publishes releases into [GitHub Packages](https://github.com/features/packages) Docker registry. To pull the image call: - -```bash -$ docker pull ghcr.io/nicklockwood/swiftformat:latest -``` - -By default, the container runs `swiftformat .` Therefore, you need to provide a path either via an argument: - -```bash -docker run --rm -v /local/source/path:/work ghcr.io/nicklockwood/swiftformat:latest /work -``` - -or by changing the working dir: - -```bash -docker run --rm -v /local/source/path:/work -w /work ghcr.io/nicklockwood/swiftformat:latest -``` - -To check the installed SwiftFormat version: - -```bash -docker run --rm ghcr.io/nicklockwood/swiftformat:latest --version -``` - -Linting example: - -```bash -docker run --rm -v /local/source/path:/work ghcr.io/nicklockwood/swiftformat:latest /work --lint -``` - -Configuration -------------- - -SwiftFormat's configuration is split between **rules** and **options**. Rules are functions in the SwiftFormat library that apply changes to the code. Options are settings that control the behavior of the rules. - - -Options -------- - -The options available in SwiftFormat can be displayed using the `--options` command-line argument. The default value for each option is indicated in the help text. - -Rules are configured by adding `--[option_name] [value]` to your command-line arguments, or by creating a `.swiftformat` [config file](#config-file) and placing it in your project directory. - -A given option may affect multiple rules. Use `--ruleinfo [rule_name]` command for details about which options affect a given rule, or see the [Rules.md](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md) file. - -You can configure options for specific files or code ranges by using `swiftformat:options` directive in comments inside your Swift file. To temporarily set one or more options inside a source file, use: - -```swift -// swiftformat:options --indent 2 --allman true -``` - -To apply an options override only to a particular line, use the `:next` modifier: - -```swift -// swiftformat:options:next --semicolons inline -doTheThing(); print("Did the thing") -``` - - -Rules ------ - -SwiftFormat includes over 50 rules, and new ones are added all the time. An up-to-date list can be found in [Rules.md](https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md) along with documentation for how they are used. - -The list of available rules can be displayed within the command-line app using the `--rules` argument. Rules can be either enabled or disabled. Most are enabled by default. Disabled rules are marked with "(disabled)" when displayed using `--rules`. - -You can use the `--ruleinfo [rule_name]` command to get information about a specific rule. Pass a comma-delimited list of rule names to get information for multiple rules at once, or use `--ruleinfo` with no argument for info on all rules. - -You can disable rules individually using `--disable` followed by a list of one or more comma-delimited rule names, or enable opt-in rules using `--enable` followed by the rule names: - -```bash ---disable redundantSelf,trailingClosures ---enable isEmpty -``` - -If you prefer, you can use multiple `--enable`/`--disable` arguments instead of using commas: - -```bash ---disable indent ---disable linebreaks ---disable redundantSelf -``` - -Alternatively, you can use the line continuation character `\` to wrap a single argument over multiple line: - -```bash ---disable \ - indent, \ - linebreaks, \ - redundantSelf -``` - -To avoid automatically opting-in to new rules when SwiftFormat is updated, you can disable all rules using: - -```bash ---disable all -``` - -And then individually enable just the rules you want. Alternatively, use the`--rules` argument to *only* enable the rules you specify: - -```bash ---rules indent,linebreaks -``` - -As above, you may include multiple `--rules` arguments, or use the line continuation character `\` to wrap the rules onto separate lines: - -```bash ---rules redundantSelf ---rules \ - indent, \ - linebreaks -``` - -To see exactly which rules were applied to a given file, you can use the `--verbose` command-line option to force SwiftFormat to print a more detailed log as it applies the formatting. **NOTE:** running in verbose mode is slower than the default mode. - -You can disable rules for specific files or code ranges by using `swiftformat:` directives in comments inside your Swift file. To temporarily disable one or more rules inside a source file, use: - -```swift -// swiftformat:disable [ [rule<3> ...]] -``` - -To enable the rule(s) again, use: - -```swift -// swiftformat:enable [ [rule<3> ...]] -``` - -To disable all rules use: - -```swift -// swiftformat:disable all -``` - -And to enable them all again, use: - -```swift -// swiftformat:enable all -``` - -To temporarily prevent one or more rules being applied to just the next line, use: - -```swift -// swiftformat:disable:next [ [rule<3> ...]] -let foo = bar // rule(s) will be disabled for this line -let bar = baz // rule(s) will be re-enabled for this line -``` - -There is no need to manually re-enable a rule after using the `next` directive. - -**NOTE:** The `swiftformat:enable` directives only serves to counter a previous `swiftformat:disable` directive in the same file. It is not possible to use `swiftformat:enable` to enable a rule that was not already enabled when formatting started. - - -Swift version -------------- - -Most SwiftFormat rules are version-agnostic, but some are applicable only to newer Swift versions. These rules will be disabled automatically if the Swift version is not specified, so to make sure that the full functionality is available you should specify the version of Swift that is used by your project. - -You can specify the Swift version in one of two ways: - -The preferred option is to add a `.swift-version` file to your project directory. This is a text file that should contain the minimum Swift version supported by your project, and is a standard already used by other tools. - -The `.swift-version` file applies hierarchically; If you have submodules in your project that use a different Swift version, you can add separate `.swift-version` files to those directories. - -The other option to specify the Swift version using the `--swiftversion` command line argument. Note that this will be overridden by any `.swift-version` files encountered while processing. - - -Config file ------------ - -Although it is possible to configure SwiftFormat directly by using the command-line [options](#options) and [rules](#rules) detailed above, it is sometimes more convenient to create a configuration file, which can be added to your project and shared with other developers. - -A SwiftFormat configuration file consists of one or more command-line options, split onto separate lines, e.g: - -``` ---allman true ---indent tab ---disable elseOnSameLine,semicolons -``` - -While formatting, SwiftFormat will automatically check inside each subdirectory for the presence of a `.swiftformat` file and will apply any options that it finds there to the files in that directory. - -This allows you to override certain rules or formatting options just for a particular directory of files. You can also specify excluded files relative to that directory using `--exclude`, which may be more convenient than specifying them at the top-level: - -``` ---exclude Pods,Generated -``` - -The `--exclude` option takes a comma-delimited list of file or directory paths to exclude from formatting. Excluded paths are relative to the config file containing the `--exclude` command. The excluded paths can include wildcards, specified using Unix "Glob" syntax, as [documented below](#globs). - -Config files named ".swiftformat" will be processed automatically, however, you can select an additional configuration file to use for formatting using the `--config "path/to/config/file"` command-line argument. A configuration file selected using `--config` does not need to be named ".swiftformat", and can be located outside of the project directory. - -The config file format is designed to be edited by hand. You may include blank lines for readability, and can also add comments using a hash prefix (#), e.g. - -``` -# format options ---allman true ---indent tab # tabs FTW! - -# file options ---exclude Pods - -# rules ---disable elseOnSameLine,semicolons -``` - -If you would prefer not to edit the configuration file by hand, you can use the [SwiftFormat for Xcode](#xcode-source-editor-extension) app to edit the configuration and export a configuration file. You can also use the swiftformat command-line-tool's `--inferoptions` command to generate a config file from your existing project, like this: - -```bash -$ cd /path/to/project -$ swiftformat --inferoptions . --output .swiftformat -``` - -Globs ------ - -When excluding files from formatting using the `--exclude` option, you may wish to make use of wildcard paths (aka "Globs") to match all files that match a particular naming convention without having to manually list them all. - -SwiftFormat's glob syntax is based on Ruby's implementation, which varies slightly from the Unix standard. The following patterns are supported: - -* `*` - A single star matches zero or more characters in a filename, but *not* a `/`. - -* `**` - A double star will match anything, including one or more `/`. - -* `?` - A question mark will match any single character except `/`. - -* `[abc]` - Matches any single character inside the brackets. - -* `[a-z]` - Matches a single character in the specified range in the brackets. - -* `{foo,bar}` - Matches any one of the comma-delimited strings inside the braces. - -Examples: - -* `foo.swift` - Matches the file "foo.swift" in the same directory as the config file. - -* `*.swift` - Matches any Swift file in the same directory as the config file. - -* `foo/bar.swift` - Matches the file "bar.swift" in the directory "foo". - -* `**/foo.swift` - Matches any file named "foo.swift" in the project. - -* `**/*.swift` - Matches any Swift file in the project. - -* `**/Generated` - Matches any folder called `Generated` in the project. - -* `**/*_generated.swift` - Matches any Swift file with the suffix "_generated" in the project. - - -Linting -------- - -SwiftFormat is primarily designed as a formatter rather than a linter, i.e. it is designed to fix your code rather than tell you what's wrong with it. However, sometimes it can be useful to verify that code has been formatted in a context where it is not desirable to actually change it. - -A typical example would be as part of a CI (Continuous Integration) process, where you may wish to have an automated script that checks committed code for style violations. While you can use a separate tool such as [SwiftLint](https://github.com/realm/SwiftLint) for this, it makes sense to be able to validate the formatting against the exact same rules as you are using to apply it. - -In order to run SwiftFormat as a linter, you can use the `--lint` command-line option: - -```bash -$ swiftformat --lint path/to/project -``` - -This runs the same rules as format mode, and all the same configuration options apply, however, no files will be modified. Instead, SwiftFormat will format each file in memory and then compare the result against the input and report the lines that required changes. - -The `--lint` option is similar to `--dryrun`, but `--lint` returns warnings for every line that required changes, and will return a nonzero error code (see [Error codes](#error-codes) below) if any changes are detected, which is useful if you want it to fail a build step on your CI server. - -If you would prefer `--lint` not to fail your build, you can use the `--lenient` option to force SwiftFormat to return success in `--lint` mode even when formatting issues were detected. - -```bash -$ swiftformat --lint --lenient path/to/project -``` - -By default, `--lint` will only report lines that require formatting, but you can use the additional `--verbose` flag to display additional info about which files were checked, even if there were no changes needed. - -If you would prefer not to see a warning for each and every formatting change, you can use the `--quiet` flag to suppress all output except errors. - -Sometimes you may wish to autoformat some rules, but only lint others. To do that, use the `--lintonly` option in your config file to specify rules that should only be applied in `--lint` mode: - -``` ---rules braces,indent ---lintonly trailingClosures,unusedArguments -``` - - -Error codes ------------ - -The swiftformat command-line tool will always exit with one of the following codes: - -* 0 - Success. This code will be returned in the event of a successful formatting run or if `--lint` detects no violations. -* 1 - Lint failure. This code will be returned only when running in `--lint` mode if the input requires formatting. -* 70 - Program error. This code will be returned if there is a problem with the input or configuration arguments. - - -Cache ------- - -SwiftFormat uses a cache file to avoid reformatting files that haven't changed. For a large project, this can significantly reduce processing time. - -By default, the cache is stored in `~/Library/Caches/com.charcoaldesign.swiftformat` on macOS, or `/var/tmp/com.charcoaldesign.swiftformat` on Linux. Use the command-line option `--cache ignore` to ignore the cached version and re-apply formatting to all files. Alternatively, you can use `--cache clear` to delete the cache (or you can just manually delete the cache file). - -The cache is shared between all projects. The file is fairly small, as it only stores the path and size for each file, not the contents. If you do start experiencing slowdown due to the cache growing too large, you might want to consider using a separate cache file for each project. - -You can specify a custom cache file location by passing a path as the `--cache` option value. For example, you might want to store the cache file inside your project directory. It is fine to check in the cache file if you want to share it between different users of your project, as the paths stored in the cache are relative to the location of the formatted files. - - -File headers -------------- - -SwiftFormat can be configured to strip or replace the header comments in every file with a template. The "header comment" is defined as a comment block that begins on the first nonblank line in the file, and is followed by at least one blank line. This may consist of a single comment body, or multiple comments on consecutive lines: - -```swift -// This is a header comment -``` - -```swift -// This is a regular comment -func foo(bar: Int) -> Void { ... } -``` - -The header template is a string that you provide using the `--header` command-line option. Passing a value of `ignore` (the default) will leave the header comments unmodified. Passing `strip` or an empty string `""` will remove them. If you wish to provide a custom header template, the format is as follows: - -For a single-line template: `--header "Copyright (c) 2017 Foobar Industries"` - -For a multiline comment, mark linebreaks with `\n`: `--header "First line\nSecond line"` - -You can optionally include Swift comment markup in the template if you wish: `--header "/*--- Header comment ---*/"` - -If you do not include comment markup, each line in the template will be prepended with `//` and a single space. - -It is common practice to include the file name, creation date and/or the current year in a comment header copyright notice. To do that, you can use the following placeholders: - -* `{file}` - the name of the file -* `{year}` - the current year -* `{created}` - the date on which the file was created -* `{created.year}` - the year in which the file was created - -For example, a header template of: - -```bash ---header "{file}\nCopyright (c) {year} Foobar Industries\nCreated by John Smith on {created}." -``` - -Will be formatted as: - -```swift -// SomeFile.swift -// Copyright (c) 2019 Foobar Industries -// Created by John Smith on 01/02/2016. -``` - -**NOTE:** the `{year}` value and `{created}` date format are determined from the current locale and timezone of the machine running the script. - - -FAQ ------ - -*Q. How is this different from SwiftLint?* - -> A. SwiftLint is primarily designed to find and report code smells and style violations in your code. SwiftFormat is designed to fix them. While SwiftLint can autocorrect some issues, and SwiftFormat has some support for [linting](#linting), their primary functions are different. - - -*Q. Can SwiftFormat and SwiftLint be used together?* - -> A. Absolutely! The style rules encouraged by both tools are quite similar, and SwiftFormat even fixes some style violations that SwiftLint warns about but can't currently autocorrect. - - -*Q. What platforms does SwiftFormat support?* - -> A. SwiftFormat works on macOS 10.13 (High Sierra) and above, and also runs on Ubuntu Linux and Windows. - - -*Q. What versions of Swift are supported?* - -> A. The SwiftFormat framework and command-line tool can be compiled using Swift 4.2 and above, and can format programs written in Swift 4.x or 5. Swift 3.x is no longer actively supported. If you are still using Swift 3.x or earlier and find that SwiftFormat breaks your code, the best solution is probably to revert to an earlier SwiftFormat release, or enable only a small subset of rules. Use the `--swiftversion` argument to enable additional rules specific to later Swift versions. - - -*Q. SwiftFormat made changes I didn't want it to. How can I find out which rules to disable?* - -> A. If you run SwiftFormat using the `--verbose` option, it will tell you which rules were applied to each file. You can then selectively disable certain rules using the `--disable` argument (see below). - - -*Q. People on my team have different SwiftFormat versions installed. How can we ensure consistent formatting? - -> A. You can specify a `--minversion` argument in your project's .swiftformat` file to fail the build if developers attempt to use an older SwiftFormat version. - - -*Q. How can I modify the formatting rules?* - -> A. Many configuration options are exposed in the command-line interface or `.swiftformat` configuration file. You can either set these manually, or use the `--inferoptions` argument to automatically generate the configuration from your existing project. - -> If there is a rule that you don't like, and which cannot be configured to your liking via the command-line options, you can disable one or more rules by using the `--disable` argument, followed by the name of the rules, separated by commas. You can display a list of all supported rules using the `--rules` argument, and their behaviors are documented above this section in the README. - -> If you are using the Xcode source editor extension, rules and options can be configured using the [SwiftFormat for Xcode](#xcode-source-editor-extension) host application. Unfortunately, due to limitation of the Extensions API, there is no way to configure these on a per-project basis. - -> If the options you want aren't exposed, and disabling the rule doesn't solve the problem, the rules are implemented in the file `Rules.swift`, so you can modify them and build a new version of the command-line tool. If you think your changes might be generally useful, make a pull request. - - -Q. I don't want to be surprised by new rules added when I upgrade SwiftFormat. How can I prevent this? - -> A. You can use the `--rules` argument to specify an exclusive list of rules to run. If new rules are added, they won't be enabled if you have specified a `--rules` list in your SwiftFormat configuration. - - -*Q. Why can't I set the indent width or choose between tabs/spaces in the [SwiftFormat for Xcode](#xcode-source-editor-extension) options?* - -> Indent width and tabs/spaces can be configured in Xcode on a per project-basis. You'll find the option under "Text Settings" in the Files inspector of the right-hand sidebar. - - -*Q. After applying SwiftFormat, my code won't compile. Is that a bug?* - -> A. SwiftFormat should ideally never break your code. Check the [known issues](#known-issues), and if it's not already listed there, or the suggested workaround doesn't solve your problem, please [open an issue on Github](https://github.com/nicklockwood/SwiftFormat/issues). - - -*Q. Can I use SwiftFormat to lint my code without changing it?* - -> A. Yes, see the [linting](#linting) section above for details. - - -*Q. Can I use the `SwiftFormat.framework` inside another app?* - -> A. Yes, the SwiftFormat framework can be included in an app or test target, and used for many kinds of parsing and processing of Swift source code besides formatting. The SwiftFormat framework is available as a [CocoaPod](https://cocoapods.org/pods/SwiftFormat) for easy integration. - - -Known issues ---------------- - -* When using the Xcode Source Editor Extension, the SwiftFormat menu sometimes disappears from Xcode. If this happens, try moving or renaming Xcode temporarily and then changing it back. Failing that, the suggestions in [this thread](https://github.com/nicklockwood/SwiftFormat/issues/494) may help. - -* The `enumNamespaces` rule replaces classes that have only static members with an `enum`. If the class is subclassed, or if there is code that depends on the class exposing certain runtime behaviors, this may break the program. To solve this you can either fix it on a per-case basis by adding a `// swiftformat:disable:next enumNamespaces` comment directive above the class declaration, or you can add `--enumnamespaces structs-only` to prevent the rule being applied to classes, or you can just disable the `enumNamespaces` rule completely. - -* The `redundantVoidReturnType` rule can inadvertently alter the type signature for closures, for example in cases where the closure calls a `@discardableResult` function. To solve this you can either fix it on a per-case basis by adding a `// swiftformat:disable:next redundantVoidReturnType` comment directive to disable the rule for a specific call site, or you can add `--closurevoid preserve` to your [configuration](#configuration) to disable the rule completely for closures (regular functions or methods aren't affected). - -* The `redundantType` rule can introduce ambiguous code in certain cases when using the default mode of `--redundanttype inferred`. This can be worked around by by using `--redundanttype explicit`, or by manually removing the redundant type reference on the affected line, or by using the `// swiftformat:disable:next redundantType` comment directive to disable the rule at the call site (or just disable the `redundantType` rule completely). - -* If a type initializer or factory method returns an implicitly unwrapped optional value then the `redundantType` rule may remove the explicit type in a situation where it's actually required. To work around this you can either use `--redundanttype explicit`, or use the `// swiftformat:disable:next redundantType` comment directive to disable the rule at the call site (or just disable the `redundantType` rule completely). - -* When using the `initCoderUnavailable` rule, if an `init` that is marked as unavailable is overridden elsewhere in the program then it will cause a compilation error. The recommended workaround is to remove the override (which shouldn't affect the program behavior if the init was really unused) or use the `// swiftformat:disable:next initCoderUnavailable` comment directive to disable the rule for the overridden init (or just disable the `initCoderUnavailable` rule completely). - -* When using the `extensionAccessControl` rule with the `--extensionacl on-extension` option, if you have public methods defined on an internal type defined in another file, the resultant public extension will no longer compile. The recommended solution is to manually remove the `public` modifier (this won't change the program behavior) or disable the `extensionAccessControl` rule. - -* When using the `preferKeyPath` rule, conversion of `compactMap { $0.foo }` to `compactMap(\.foo)` or `flatMap { $0.foo }` to `flatMap(\.foo)` will result in code that fails to compile if `foo` is not an `Optional` property. This is due to a difference in the way that Swift handles type inference for closures vs keyPaths, as discussed [here](https://bugs.swift.org/browse/SR-13347). The recommended workaround is to replace `compactMap()` or `flatMap()` with `map()` in these cases, which will not change the behavior of the code. - -* When using the `--self remove` option, the `redundantSelf` rule will remove references to `self` in autoclosure arguments, which may change the meaning of the code, or cause it not to compile. To work around this issue, use the `--selfrequired` option to provide a comma-delimited list of methods to be excluded from the rule. The `expect()` function from the popular [Nimble](https://github.com/Quick/Nimble) unit testing framework is already excluded by default. If you are using the `--self insert` option then this is not an issue. - -* If you assign `SomeClass.self` to a variable and then instantiate an instance of the class using that variable, Swift requires that you use an explicit `.init()`, however, the `redundantInit` rule is not currently capable of detecting this situation in all cases, and may remove the `.init`. To work around this issue, use the `// swiftformat:disable:next redundantInit` comment directive to disable the rule for any affected lines of code (or just disable the `redundantInit` rule completely). - -* The `--self insert` option can only recognize locally declared member variables, not ones inherited from superclasses or extensions in other files, so it cannot insert missing `self` references for those. Note that the reverse is not true: `--self remove` should remove *all* redundant `self` references. - -* The `trailingClosures` rule can generate ambiguous code if a function has multiple optional closure arguments, or if multiple functions have signatures differing only by the name of the closure argument. For this reason, the rule is limited to anonymous closure arguments by default. You can use the `--trailingclosures` and `--nevertrailing` arguments to explicitly opt in or out of trailing closure support for specific functions. - -* The `isEmpty` rule will convert `count == 0` to `isEmpty` even for types that do not have an `isEmpty` method, such as `NSArray`/`NSDictionary`/etc. Use of Foundation collections in Swift code is pretty rare, but just in case, the rule is disabled by default. - -* If a file begins with a comment, the `stripHeaders` rule will remove it if it is followed by a blank line. To avoid this, make sure that the first comment is directly followed by a line of code. - -* When running a version of SwiftFormat built using Xcode 10.2 on macOS 10.14.3 or earlier, you may experience a crash with the error "dyld: Library not loaded: @rpath/libswiftCore.dylib". To fix this, you need to install the [Swift 5 Runtime Support for Command Line Tools](https://support.apple.com/kb/DL1998). These tools are included by default in macOS 10.14.4 and later. - - -Tip Jar ------------ - -SwiftFormat is not a commercially-funded product, it's a labor of love given freely to the community. If you find it useful, please consider making a donation. - -[![Donate via PayPal](https://www.paypalobjects.com/en_GB/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9ZGWNK5FEZFF6&source=url) - - -Credits ------------- - -* [Tony Arnold](https://github.com/tonyarnold) - SwiftFormat for Xcode -* [Vincent Bernier](https://github.com/vinceburn) - SwiftFormat for Xcode settings UI -* [Vikram Kriplaney](https://github.com/markiv) - SwiftFormat for Xcode icon and search feature -* [Hyperphonic](https://github.com/hyperphonic0) - Xcode 12 compatibility for SwiftFormat -* [Maxime Marinel](https://github.com/bourvill) - Git pre-commit hook script -* [Romain Pouclet](https://github.com/palleas) - Homebrew formula -* [Aerobounce](https://github.com/aerobounce) - Homebrew cask and Sublime Text plugin -* [Cal Stephens](https://github.com/calda) - Several new formatting rules and options -* [Facundo Menzella](https://github.com/acumenzella) - Several new formatting rules -* [Ali Akhtarzada](https://github.com/aliak00) - Several path-related CLI enhancements -* [Yonas Kolb](https://github.com/yonaskolb) - Swift Package Manager integration -* [Wolfgang Lutz](https://github.com/Lutzifer) - AppleScript integration instructions -* [BalĂĄzs KilvĂĄdy](https://github.com/balitm) - Xcode lint warning integration -* [Anthony Miller](https://github.com/AnthonyMDev) - Improvements to wrap/indent logic -* [Shingo Takagi](https://github.com/zizi4n5) - Several brace-related bug fixes -* [Benedek Kozma](https://github.com/cyberbeni) - Lint-only rules option -* [Juri Pakaste](https://github.com/juri) - Filelist feature -* [Jim Puls](https://github.com/puls) - Big Sur icon update -* [Daniele Formichelli](https://github.com/danyf90) - JSON reporter -* [Mahdi Bchatnia](https://github.com/inket) - Linux build workflow -* [Arthur Semenyutin](https://github.com/vox-humana) - Docker image -* [Nick Lockwood](https://github.com/nicklockwood) - Everything else - -([Full list of contributors](https://github.com/nicklockwood/SwiftFormat/graphs/contributors)) diff --git a/Example/Pods/SwiftLint/LICENSE b/Example/Pods/SwiftLint/LICENSE deleted file mode 100644 index 0420376..0000000 --- a/Example/Pods/SwiftLint/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2020 Realm Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Example/Pods/SwiftLint/swiftlint b/Example/Pods/SwiftLint/swiftlint deleted file mode 100755 index 3210a21..0000000 Binary files a/Example/Pods/SwiftLint/swiftlint and /dev/null differ diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Info.plist b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Info.plist deleted file mode 100644 index 4522675..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 3.0.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-Info.plist b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-Info.plist deleted file mode 100644 index 45f6ca7..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks-input-files.xcfilelist b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks-input-files.xcfilelist deleted file mode 100644 index 40274a1..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks-input-files.xcfilelist +++ /dev/null @@ -1,3 +0,0 @@ -${PODS_ROOT}/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks.sh -${BUILT_PRODUCTS_DIR}/GradientLoadingBar/GradientLoadingBar.framework -${BUILT_PRODUCTS_DIR}/SnapshotTesting/SnapshotTesting.framework \ No newline at end of file diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks-output-files.xcfilelist b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks-output-files.xcfilelist deleted file mode 100644 index 6c4b2fb..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks-output-files.xcfilelist +++ /dev/null @@ -1,2 +0,0 @@ -${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GradientLoadingBar.framework -${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapshotTesting.framework \ No newline at end of file diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks.sh b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks.sh deleted file mode 100755 index 264149d..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-frameworks.sh +++ /dev/null @@ -1,188 +0,0 @@ -#!/bin/sh -set -e -set -u -set -o pipefail - -function on_error { - echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" -} -trap 'on_error $LINENO' ERR - -if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then - # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy - # frameworks to, so exit 0 (signalling the script phase was successful). - exit 0 -fi - -echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" -mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - -COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" -SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" -BCSYMBOLMAP_DIR="BCSymbolMaps" - - -# This protects against multiple targets copying the same framework dependency at the same time. The solution -# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html -RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") - -# Copies and strips a vendored framework -install_framework() -{ - if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then - local source="${BUILT_PRODUCTS_DIR}/$1" - elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then - local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" - elif [ -r "$1" ]; then - local source="$1" - fi - - local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - - if [ -L "${source}" ]; then - echo "Symlinked..." - source="$(readlink "${source}")" - fi - - if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then - # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied - find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do - echo "Installing $f" - install_bcsymbolmap "$f" "$destination" - rm "$f" - done - rmdir "${source}/${BCSYMBOLMAP_DIR}" - fi - - # Use filter instead of exclude so missing patterns don't throw errors. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" - - local basename - basename="$(basename -s .framework "$1")" - binary="${destination}/${basename}.framework/${basename}" - - if ! [ -r "$binary" ]; then - binary="${destination}/${basename}" - elif [ -L "${binary}" ]; then - echo "Destination binary is symlinked..." - dirname="$(dirname "${binary}")" - binary="${dirname}/$(readlink "${binary}")" - fi - - # Strip invalid architectures so "fat" simulator / device frameworks work on device - if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then - strip_invalid_archs "$binary" - fi - - # Resign the code if required by the build settings to avoid unstable apps - code_sign_if_enabled "${destination}/$(basename "$1")" - - # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. - if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then - local swift_runtime_libs - swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) - for lib in $swift_runtime_libs; do - echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" - rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" - code_sign_if_enabled "${destination}/${lib}" - done - fi -} -# Copies and strips a vendored dSYM -install_dsym() { - local source="$1" - warn_missing_arch=${2:-true} - if [ -r "$source" ]; then - # Copy the dSYM into the targets temp dir. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" - - local basename - basename="$(basename -s .dSYM "$source")" - binary_name="$(ls "$source/Contents/Resources/DWARF")" - binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" - - # Strip invalid architectures from the dSYM. - if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then - strip_invalid_archs "$binary" "$warn_missing_arch" - fi - if [[ $STRIP_BINARY_RETVAL == 0 ]]; then - # Move the stripped file into its final destination. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" - else - # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. - mkdir -p "${DWARF_DSYM_FOLDER_PATH}" - touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" - fi - fi -} - -# Used as a return value for each invocation of `strip_invalid_archs` function. -STRIP_BINARY_RETVAL=0 - -# Strip invalid architectures -strip_invalid_archs() { - binary="$1" - warn_missing_arch=${2:-true} - # Get architectures for current target binary - binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" - # Intersect them with the architectures we are building for - intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" - # If there are no archs supported by this binary then warn the user - if [[ -z "$intersected_archs" ]]; then - if [[ "$warn_missing_arch" == "true" ]]; then - echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." - fi - STRIP_BINARY_RETVAL=1 - return - fi - stripped="" - for arch in $binary_archs; do - if ! [[ "${ARCHS}" == *"$arch"* ]]; then - # Strip non-valid architectures in-place - lipo -remove "$arch" -output "$binary" "$binary" - stripped="$stripped $arch" - fi - done - if [[ "$stripped" ]]; then - echo "Stripped $binary of architectures:$stripped" - fi - STRIP_BINARY_RETVAL=0 -} - -# Copies the bcsymbolmap files of a vendored framework -install_bcsymbolmap() { - local bcsymbolmap_path="$1" - local destination="${BUILT_PRODUCTS_DIR}" - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" -} - -# Signs a framework with the provided identity -code_sign_if_enabled() { - if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then - # Use the current code_sign_identity - echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" - local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" - - if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then - code_sign_cmd="$code_sign_cmd &" - fi - echo "$code_sign_cmd" - eval "$code_sign_cmd" - fi -} - -if [[ "$CONFIGURATION" == "Debug" ]]; then - install_framework "${BUILT_PRODUCTS_DIR}/GradientLoadingBar/GradientLoadingBar.framework" - install_framework "${BUILT_PRODUCTS_DIR}/SnapshotTesting/SnapshotTesting.framework" -fi -if [[ "$CONFIGURATION" == "Release" ]]; then - install_framework "${BUILT_PRODUCTS_DIR}/GradientLoadingBar/GradientLoadingBar.framework" - install_framework "${BUILT_PRODUCTS_DIR}/SnapshotTesting/SnapshotTesting.framework" -fi -if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then - wait -fi diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-prefix.pch b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-prefix.pch deleted file mode 100644 index beb2a24..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-Unit-Tests-prefix.pch +++ /dev/null @@ -1,12 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-dummy.m b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-dummy.m deleted file mode 100644 index 930fffb..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-dummy.m +++ /dev/null @@ -1,5 +0,0 @@ -#import -@interface PodsDummy_GradientLoadingBar : NSObject -@end -@implementation PodsDummy_GradientLoadingBar -@end diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-prefix.pch b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-prefix.pch deleted file mode 100644 index beb2a24..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-prefix.pch +++ /dev/null @@ -1,12 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-umbrella.h b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-umbrella.h deleted file mode 100644 index afb2dcf..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar-umbrella.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - - -FOUNDATION_EXPORT double GradientLoadingBarVersionNumber; -FOUNDATION_EXPORT const unsigned char GradientLoadingBarVersionString[]; - diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.debug.xcconfig b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.debug.xcconfig deleted file mode 100644 index a8453da..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.debug.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.modulemap b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.modulemap deleted file mode 100644 index bcee2d1..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module GradientLoadingBar { - umbrella header "GradientLoadingBar-umbrella.h" - - export * - module * { export * } -} diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.release.xcconfig b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.release.xcconfig deleted file mode 100644 index a8453da..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.release.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.unit-tests.debug.xcconfig b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.unit-tests.debug.xcconfig deleted file mode 100644 index cb06089..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.unit-tests.debug.xcconfig +++ /dev/null @@ -1,15 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar" "${PODS_CONFIGURATION_BUILD_DIR}/SnapshotTesting" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks' -LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift -OTHER_LDFLAGS = $(inherited) -ObjC -framework "GradientLoadingBar" -framework "SnapshotTesting" -framework "XCTest" -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.unit-tests.release.xcconfig b/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.unit-tests.release.xcconfig deleted file mode 100644 index cb06089..0000000 --- a/Example/Pods/Target Support Files/GradientLoadingBar/GradientLoadingBar.unit-tests.release.xcconfig +++ /dev/null @@ -1,15 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar" "${PODS_CONFIGURATION_BUILD_DIR}/SnapshotTesting" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks' -LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift -OTHER_LDFLAGS = $(inherited) -ObjC -framework "GradientLoadingBar" -framework "SnapshotTesting" -framework "XCTest" -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-Info.plist b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-Info.plist deleted file mode 100644 index 2243fe6..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown deleted file mode 100644 index 1674c39..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown +++ /dev/null @@ -1,98 +0,0 @@ -# Acknowledgements -This application makes use of the following third party libraries: - -## GradientLoadingBar - -Copyright (c) 2017-2020 Felix Mau - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - -## SwiftConfigurationFiles - -Copyright (c) 2020 Felix Mau - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -## SwiftFormat - -MIT License - -Copyright (c) 2016 Nick Lockwood - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - -## SwiftLint - -The MIT License (MIT) - -Copyright (c) 2020 Realm Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -Generated by CocoaPods - https://cocoapods.org diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.plist b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.plist deleted file mode 100644 index 46a33b7..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.plist +++ /dev/null @@ -1,148 +0,0 @@ - - - - - PreferenceSpecifiers - - - FooterText - This application makes use of the following third party libraries: - Title - Acknowledgements - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2017-2020 Felix Mau <me@felix.hamburg> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - License - MIT - Title - GradientLoadingBar - Type - PSGroupSpecifier - - - FooterText - Copyright (c) 2020 Felix Mau <me@felix.hamburg> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - License - MIT - Title - SwiftConfigurationFiles - Type - PSGroupSpecifier - - - FooterText - MIT License - -Copyright (c) 2016 Nick Lockwood - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - License - MIT - Title - SwiftFormat - Type - PSGroupSpecifier - - - FooterText - The MIT License (MIT) - -Copyright (c) 2020 Realm Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - License - MIT - Title - SwiftLint - Type - PSGroupSpecifier - - - FooterText - Generated by CocoaPods - https://cocoapods.org - Title - - Type - PSGroupSpecifier - - - StringsTable - Acknowledgements - Title - Acknowledgements - - diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m deleted file mode 100644 index 6ee3f90..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m +++ /dev/null @@ -1,5 +0,0 @@ -#import -@interface PodsDummy_Pods_Example : NSObject -@end -@implementation PodsDummy_Pods_Example -@end diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Debug-input-files.xcfilelist b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Debug-input-files.xcfilelist deleted file mode 100644 index 0fdafe1..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Debug-input-files.xcfilelist +++ /dev/null @@ -1,2 +0,0 @@ -${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh -${BUILT_PRODUCTS_DIR}/GradientLoadingBar/GradientLoadingBar.framework \ No newline at end of file diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Debug-output-files.xcfilelist b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Debug-output-files.xcfilelist deleted file mode 100644 index f86f308..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Debug-output-files.xcfilelist +++ /dev/null @@ -1 +0,0 @@ -${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GradientLoadingBar.framework \ No newline at end of file diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Release-input-files.xcfilelist b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Release-input-files.xcfilelist deleted file mode 100644 index 0fdafe1..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Release-input-files.xcfilelist +++ /dev/null @@ -1,2 +0,0 @@ -${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh -${BUILT_PRODUCTS_DIR}/GradientLoadingBar/GradientLoadingBar.framework \ No newline at end of file diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Release-output-files.xcfilelist b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Release-output-files.xcfilelist deleted file mode 100644 index f86f308..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks-Release-output-files.xcfilelist +++ /dev/null @@ -1 +0,0 @@ -${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GradientLoadingBar.framework \ No newline at end of file diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks.sh b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks.sh deleted file mode 100755 index 1406835..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks.sh +++ /dev/null @@ -1,186 +0,0 @@ -#!/bin/sh -set -e -set -u -set -o pipefail - -function on_error { - echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" -} -trap 'on_error $LINENO' ERR - -if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then - # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy - # frameworks to, so exit 0 (signalling the script phase was successful). - exit 0 -fi - -echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" -mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - -COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" -SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" -BCSYMBOLMAP_DIR="BCSymbolMaps" - - -# This protects against multiple targets copying the same framework dependency at the same time. The solution -# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html -RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") - -# Copies and strips a vendored framework -install_framework() -{ - if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then - local source="${BUILT_PRODUCTS_DIR}/$1" - elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then - local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" - elif [ -r "$1" ]; then - local source="$1" - fi - - local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" - - if [ -L "${source}" ]; then - echo "Symlinked..." - source="$(readlink "${source}")" - fi - - if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then - # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied - find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do - echo "Installing $f" - install_bcsymbolmap "$f" "$destination" - rm "$f" - done - rmdir "${source}/${BCSYMBOLMAP_DIR}" - fi - - # Use filter instead of exclude so missing patterns don't throw errors. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" - - local basename - basename="$(basename -s .framework "$1")" - binary="${destination}/${basename}.framework/${basename}" - - if ! [ -r "$binary" ]; then - binary="${destination}/${basename}" - elif [ -L "${binary}" ]; then - echo "Destination binary is symlinked..." - dirname="$(dirname "${binary}")" - binary="${dirname}/$(readlink "${binary}")" - fi - - # Strip invalid architectures so "fat" simulator / device frameworks work on device - if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then - strip_invalid_archs "$binary" - fi - - # Resign the code if required by the build settings to avoid unstable apps - code_sign_if_enabled "${destination}/$(basename "$1")" - - # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. - if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then - local swift_runtime_libs - swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) - for lib in $swift_runtime_libs; do - echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" - rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" - code_sign_if_enabled "${destination}/${lib}" - done - fi -} -# Copies and strips a vendored dSYM -install_dsym() { - local source="$1" - warn_missing_arch=${2:-true} - if [ -r "$source" ]; then - # Copy the dSYM into the targets temp dir. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" - - local basename - basename="$(basename -s .dSYM "$source")" - binary_name="$(ls "$source/Contents/Resources/DWARF")" - binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" - - # Strip invalid architectures from the dSYM. - if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then - strip_invalid_archs "$binary" "$warn_missing_arch" - fi - if [[ $STRIP_BINARY_RETVAL == 0 ]]; then - # Move the stripped file into its final destination. - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" - else - # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. - mkdir -p "${DWARF_DSYM_FOLDER_PATH}" - touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" - fi - fi -} - -# Used as a return value for each invocation of `strip_invalid_archs` function. -STRIP_BINARY_RETVAL=0 - -# Strip invalid architectures -strip_invalid_archs() { - binary="$1" - warn_missing_arch=${2:-true} - # Get architectures for current target binary - binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" - # Intersect them with the architectures we are building for - intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" - # If there are no archs supported by this binary then warn the user - if [[ -z "$intersected_archs" ]]; then - if [[ "$warn_missing_arch" == "true" ]]; then - echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." - fi - STRIP_BINARY_RETVAL=1 - return - fi - stripped="" - for arch in $binary_archs; do - if ! [[ "${ARCHS}" == *"$arch"* ]]; then - # Strip non-valid architectures in-place - lipo -remove "$arch" -output "$binary" "$binary" - stripped="$stripped $arch" - fi - done - if [[ "$stripped" ]]; then - echo "Stripped $binary of architectures:$stripped" - fi - STRIP_BINARY_RETVAL=0 -} - -# Copies the bcsymbolmap files of a vendored framework -install_bcsymbolmap() { - local bcsymbolmap_path="$1" - local destination="${BUILT_PRODUCTS_DIR}" - echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" - rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" -} - -# Signs a framework with the provided identity -code_sign_if_enabled() { - if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then - # Use the current code_sign_identity - echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" - local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" - - if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then - code_sign_cmd="$code_sign_cmd &" - fi - echo "$code_sign_cmd" - eval "$code_sign_cmd" - fi -} - -if [[ "$CONFIGURATION" == "Debug" ]]; then - install_framework "${BUILT_PRODUCTS_DIR}/GradientLoadingBar/GradientLoadingBar.framework" -fi -if [[ "$CONFIGURATION" == "Release" ]]; then - install_framework "${BUILT_PRODUCTS_DIR}/GradientLoadingBar/GradientLoadingBar.framework" -fi -if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then - wait -fi diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-umbrella.h b/Example/Pods/Target Support Files/Pods-Example/Pods-Example-umbrella.h deleted file mode 100644 index ecf498e..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example-umbrella.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - - -FOUNDATION_EXPORT double Pods_ExampleVersionNumber; -FOUNDATION_EXPORT const unsigned char Pods_ExampleVersionString[]; - diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig b/Example/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig deleted file mode 100644 index 1baabba..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig +++ /dev/null @@ -1,15 +0,0 @@ -ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar/GradientLoadingBar.framework/Headers" -LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' -LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift -OTHER_LDFLAGS = $(inherited) -framework "GradientLoadingBar" -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_PODFILE_DIR_PATH = ${SRCROOT}/. -PODS_ROOT = ${SRCROOT}/Pods -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example.modulemap b/Example/Pods/Target Support Files/Pods-Example/Pods-Example.modulemap deleted file mode 100644 index 4b5189f..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module Pods_Example { - umbrella header "Pods-Example-umbrella.h" - - export * - module * { export * } -} diff --git a/Example/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig b/Example/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig deleted file mode 100644 index 1baabba..0000000 --- a/Example/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig +++ /dev/null @@ -1,15 +0,0 @@ -ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/GradientLoadingBar/GradientLoadingBar.framework/Headers" -LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' -LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift -OTHER_LDFLAGS = $(inherited) -framework "GradientLoadingBar" -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_PODFILE_DIR_PATH = ${SRCROOT}/. -PODS_ROOT = ${SRCROOT}/Pods -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-Info.plist b/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-Info.plist deleted file mode 100644 index 62cf7b2..0000000 --- a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.9.0 - CFBundleSignature - ???? - CFBundleVersion - ${CURRENT_PROJECT_VERSION} - NSPrincipalClass - - - diff --git a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-dummy.m b/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-dummy.m deleted file mode 100644 index 30dbacc..0000000 --- a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-dummy.m +++ /dev/null @@ -1,5 +0,0 @@ -#import -@interface PodsDummy_SnapshotTesting : NSObject -@end -@implementation PodsDummy_SnapshotTesting -@end diff --git a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-prefix.pch b/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-prefix.pch deleted file mode 100644 index beb2a24..0000000 --- a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-prefix.pch +++ /dev/null @@ -1,12 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - diff --git a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-umbrella.h b/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-umbrella.h deleted file mode 100644 index cf2e3d0..0000000 --- a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting-umbrella.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifdef __OBJC__ -#import -#else -#ifndef FOUNDATION_EXPORT -#if defined(__cplusplus) -#define FOUNDATION_EXPORT extern "C" -#else -#define FOUNDATION_EXPORT extern -#endif -#endif -#endif - - -FOUNDATION_EXPORT double SnapshotTestingVersionNumber; -FOUNDATION_EXPORT const unsigned char SnapshotTestingVersionString[]; - diff --git a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.debug.xcconfig b/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.debug.xcconfig deleted file mode 100644 index 6aac27b..0000000 --- a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.debug.xcconfig +++ /dev/null @@ -1,18 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SnapshotTesting -ENABLE_BITCODE = NO -FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift -OTHER_LDFLAGS = $(inherited) -framework "XCTest" -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/SnapshotTesting -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -SWIFT_INCLUDE_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib" -SYSTEM_FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.modulemap b/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.modulemap deleted file mode 100644 index a8d7c9f..0000000 --- a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.modulemap +++ /dev/null @@ -1,6 +0,0 @@ -framework module SnapshotTesting { - umbrella header "SnapshotTesting-umbrella.h" - - export * - module * { export * } -} diff --git a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.release.xcconfig b/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.release.xcconfig deleted file mode 100644 index 6aac27b..0000000 --- a/Example/Pods/Target Support Files/SnapshotTesting/SnapshotTesting.release.xcconfig +++ /dev/null @@ -1,18 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SnapshotTesting -ENABLE_BITCODE = NO -FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift -OTHER_LDFLAGS = $(inherited) -framework "XCTest" -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/SnapshotTesting -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -SWIFT_INCLUDE_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/usr/lib" -SYSTEM_FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/SwiftConfigurationFiles/SwiftConfigurationFiles.debug.xcconfig b/Example/Pods/Target Support Files/SwiftConfigurationFiles/SwiftConfigurationFiles.debug.xcconfig deleted file mode 100644 index b497024..0000000 --- a/Example/Pods/Target Support Files/SwiftConfigurationFiles/SwiftConfigurationFiles.debug.xcconfig +++ /dev/null @@ -1,11 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftConfigurationFiles -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftConfigurationFiles -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/SwiftConfigurationFiles/SwiftConfigurationFiles.release.xcconfig b/Example/Pods/Target Support Files/SwiftConfigurationFiles/SwiftConfigurationFiles.release.xcconfig deleted file mode 100644 index b497024..0000000 --- a/Example/Pods/Target Support Files/SwiftConfigurationFiles/SwiftConfigurationFiles.release.xcconfig +++ /dev/null @@ -1,11 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftConfigurationFiles -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftConfigurationFiles -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.debug.xcconfig b/Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.debug.xcconfig deleted file mode 100644 index d431b4c..0000000 --- a/Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.debug.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftFormat -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.release.xcconfig b/Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.release.xcconfig deleted file mode 100644 index d431b4c..0000000 --- a/Example/Pods/Target Support Files/SwiftFormat/SwiftFormat.release.xcconfig +++ /dev/null @@ -1,13 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift -OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftFormat -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/SwiftLint/SwiftLint.debug.xcconfig b/Example/Pods/Target Support Files/SwiftLint/SwiftLint.debug.xcconfig deleted file mode 100644 index 003a1f4..0000000 --- a/Example/Pods/Target Support Files/SwiftLint/SwiftLint.debug.xcconfig +++ /dev/null @@ -1,11 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/SwiftLint/SwiftLint.release.xcconfig b/Example/Pods/Target Support Files/SwiftLint/SwiftLint.release.xcconfig deleted file mode 100644 index 003a1f4..0000000 --- a/Example/Pods/Target Support Files/SwiftLint/SwiftLint.release.xcconfig +++ /dev/null @@ -1,11 +0,0 @@ -CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO -CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 -PODS_BUILD_DIR = ${BUILD_DIR} -PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) -PODS_ROOT = ${SRCROOT} -PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint -PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates -PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} -SKIP_INSTALL = YES -USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/fastlane/Appfile b/Example/fastlane/Appfile deleted file mode 100644 index 1803063..0000000 --- a/Example/fastlane/Appfile +++ /dev/null @@ -1,6 +0,0 @@ -# app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app -# apple_id("[[APPLE_ID]]") # Your Apple email address - - -# For more information about the Appfile, see: -# https://docs.fastlane.tools/advanced/#appfile diff --git a/Example/fastlane/Fastfile b/Example/fastlane/Fastfile deleted file mode 100644 index 352ce56..0000000 --- a/Example/fastlane/Fastfile +++ /dev/null @@ -1,76 +0,0 @@ -# This file contains the fastlane.tools configuration -# You can find the documentation at https://docs.fastlane.tools -# -# For a list of all available actions, check out -# -# https://docs.fastlane.tools/actions -# -# For a list of all available plugins, check out -# -# https://docs.fastlane.tools/plugins/available-plugins -# - -# Uncomment the line if you want fastlane to automatically update itself -# update_fastlane - -default_platform(:ios) - -platform :ios do - desc "Execute SwiftFormat and treat any formatting errors as real errors." - lane :format do - swiftformat( - executable: "Pods/SwiftFormat/CommandLineTool/swiftformat", - config: "Pods/SwiftConfigurationFiles/.swiftformat", - path: "../", - lint: true - ) - end - - desc "Execute SwiftLint and treat any formatting errors as real errors." - lane :lint do - swiftlint( - config_file: ".swiftlint.yml", - strict: true, - executable: "Pods/SwiftLint/swiftlint" - ) - end - - desc "Verify Carthage support by making sure the scheme `GradientLoadingBar` is shared." - lane :verify_carthage do - # - # For Carthage support we explicitly have to mark the scheme `GradientLoadingBar` as shared. - # Source: https://www.amerhukic.com/how-to-add-carthage-support-in-existing-cocoapod-project - # - build_app( - project: "Pods/Pods.xcodeproj", - scheme: "GradientLoadingBar", - skip_archive: true - ) - end - - desc "Execute tests." - lane :tests do - run_tests( - project: "Pods/Pods.xcodeproj", - scheme: "GradientLoadingBar-Unit-Tests", - devices: ["iPhone 14"], - code_coverage: true, - number_of_retries: 1 - ) - end - - desc "Execute validation of library." - lane :pod_lint do - Dir.chdir("..") do - # - # Move outside of `Example/` directory. - # - # - Note: We skip running the tests here, as we have a separate lane for this which allows better customization. - # - pod_lib_lint( - verbose: true, - skip_tests: true - ) - end - end -end diff --git a/Example/fastlane/Pluginfile b/Example/fastlane/Pluginfile deleted file mode 100644 index c9eb1c3..0000000 --- a/Example/fastlane/Pluginfile +++ /dev/null @@ -1,5 +0,0 @@ -# Autogenerated by fastlane -# -# Ensure this file is checked in to source control! - -gem 'fastlane-plugin-swiftformat' diff --git a/GradientLoadingBar.podspec b/GradientLoadingBar.podspec deleted file mode 100644 index 3fa90f9..0000000 --- a/GradientLoadingBar.podspec +++ /dev/null @@ -1,50 +0,0 @@ -# -# Be sure to run `pod lib lint GradientLoadingBar.podspec' to ensure this is a -# valid spec before submitting. -# -# Any lines starting with a # are optional, but their use is encouraged -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html -# - -Pod::Spec.new do |s| - s.name = 'GradientLoadingBar' - s.version = '3.0.0' - s.summary = 'A customizable animated gradient loading bar.' - -# This description is used to generate tags and improve search results. -# * Think: What does it do? Why did you write it? What is the focus? -# * Try to keep it short, snappy and to the point. -# * Write the description between the DESC delimiters below. -# * Finally, don't worry about the indent, CocoaPods strips it! - - s.description = <<-DESC -A customizable animated gradient loading bar. -Inspired by https://codepen.io/marcobiedermann/pen/LExXWW - DESC - - s.homepage = 'https://github.com/fxm90/GradientLoadingBar' - s.screenshots = 'https://raw.githubusercontent.com/fxm90/GradientLoadingBar/master/Assets/screen.gif' - s.license = { :type => 'MIT', :file => 'LICENSE' } - s.author = { 'Felix Mau' => 'contact@felix.hamburg' } - s.source = { :git => 'https://github.com/fxm90/GradientLoadingBar.git', :tag => s.version.to_s } - - s.swift_version = '5.5' - s.ios.deployment_target = '13.0' - - s.source_files = 'GradientLoadingBar/Sources/**/*.swift' - - s.test_spec 'Tests' do |test_spec| - test_spec.requires_app_host = true - test_spec.source_files = 'GradientLoadingBar/Tests/**/*.{swift,md}' - test_spec.dependency 'SnapshotTesting', '~> 1.9' - end - - # s.resource_bundles = { - # 'GradientLoadingBar' => ['GradientLoadingBar/Assets/*.png'] - # } - - # s.public_header_files = 'Pod/Classes/**/*.h' - # s.frameworks = 'UIKit', 'MapKit' - # s.dependency 'LightweightObservable', '~> 2.1' - -end diff --git a/GradientLoadingBar/Sources/Feature/GradientActivityIndicatorView/GradientActivityIndicatorView+AnimateIsHidden.swift b/GradientLoadingBar/Sources/Feature/GradientActivityIndicatorView/GradientActivityIndicatorView+AnimateIsHidden.swift deleted file mode 100644 index 7abcdb8..0000000 --- a/GradientLoadingBar/Sources/Feature/GradientActivityIndicatorView/GradientActivityIndicatorView+AnimateIsHidden.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// GradientActivityIndicatorView+AnimateIsHidden.swift -// GradientLoadingBar -// -// Created by Felix Mau on 15.12.18. -// Copyright © 2018 Felix Mau. All rights reserved. -// - -import UIKit - -/// Helper methods to fade-in and -out the `GradientActivityIndicatorView` and update the `isHidden` flag -/// accordingly, as the progress-animation is started and stopped based on this flag. -/// -/// - Note: We add these methods as public extensions on `GradientActivityIndicatorView` instead of `UIView`, -/// in order to avoid conflicts with other frameworks. -/// -/// - SeeAlso: [Github Gist – UIView+AnimateIsHidden.swift](https://gist.github.com/fxm90/723b5def31b46035cd92a641e3b184f6) -public extension GradientActivityIndicatorView { - - /// Updates the view visibility. - /// - /// - Parameters: - /// - isHidden: The new view visibility. - /// - duration: The duration of the animation, measured in seconds. - /// - completion: Closure to be executed when the animation sequence ends. This block has no return value and takes a single Boolean - /// argument that indicates whether or not the animations actually finished before the completion handler was called. - /// - /// - SeeAlso: https://developer.apple.com/documentation/uikit/uiview/1622515-animatewithduration - func animate(isHidden: Bool, duration: TimeInterval, completion: ((Bool) -> Void)? = nil) { - if isHidden { - fadeOut(duration: duration, - completion: completion) - } else { - fadeIn(duration: duration, - completion: completion) - } - } - - /// Fade out the current view by animating the `alpha` to zero and update the `isHidden` flag accordingly. - /// - /// - Parameters: - /// - duration: The duration of the animation, measured in seconds. - /// - completion: Closure to be executed when the animation sequence ends. This block has no return value and takes a single Boolean - /// argument that indicates whether or not the animations actually finished before the completion handler was called. - /// - /// - SeeAlso: https://developer.apple.com/documentation/uikit/uiview/1622515-animatewithduration - func fadeOut(duration: TimeInterval = TimeInterval.GradientLoadingBar.fadeOutDuration, completion: ((Bool) -> Void)? = nil) { - UIView.animate(withDuration: duration, - animations: { - self.alpha = 0 - }, - completion: { isFinished in - // Update `isHidden` flag accordingly: - // - set to `true` in case animation was completely finished. - // - set to `false` in case animation was interrupted, e.g. due to starting of another animation. - self.isHidden = isFinished - - completion?(isFinished) - }) - } - - /// Fade in the current view by setting the `isHidden` flag to `false` and animating the `alpha` to one. - /// - /// - Parameters: - /// - duration: The duration of the animation, measured in seconds. - /// - completion: Closure to be executed when the animation sequence ends. This block has no return value and takes a single Boolean - /// argument that indicates whether or not the animations actually finished before the completion handler was called. - /// - /// - SeeAlso: https://developer.apple.com/documentation/uikit/uiview/1622515-animatewithduration - func fadeIn(duration: TimeInterval = TimeInterval.GradientLoadingBar.fadeInDuration, completion: ((Bool) -> Void)? = nil) { - if isHidden { - // Make sure our animation is visible. - isHidden = false - } - - UIView.animate(withDuration: duration, - animations: { - self.alpha = 1 - }, - completion: completion) - } -} diff --git a/GradientLoadingBar/Sources/Feature/GradientActivityIndicatorView/GradientActivityIndicatorView.swift b/GradientLoadingBar/Sources/Feature/GradientActivityIndicatorView/GradientActivityIndicatorView.swift deleted file mode 100644 index bb63449..0000000 --- a/GradientLoadingBar/Sources/Feature/GradientActivityIndicatorView/GradientActivityIndicatorView.swift +++ /dev/null @@ -1,137 +0,0 @@ -// -// GradientActivityIndicatorView.swift -// GradientLoadingBar -// -// Created by Felix Mau on 12.10.16. -// Copyright © 2016 Felix Mau. All rights reserved. -// - -import Combine -import UIKit - -@IBDesignable -open class GradientActivityIndicatorView: UIView { - - // MARK: - Config - - /// Animation-Key for the progress animation. - private enum Config { - static let progressAnimationKey = "GradientActivityIndicatorView--progressAnimation" - } - - // MARK: - Public properties - - /// Boolean flag, whether the view is currently hidden. - override open var isHidden: Bool { - didSet { - viewModel.isHidden = isHidden - } - } - - /// Colors used for the gradient. - public var gradientColors: [UIColor] { - get { viewModel.gradientColors } - set { viewModel.gradientColors = newValue } - } - - /// Duration for the progress animation. - public var progressAnimationDuration: TimeInterval { - get { viewModel.progressAnimationDuration } - set { viewModel.progressAnimationDuration = newValue } - } - - // MARK: - Private properties - - /// The layer holding the gradient. - private let gradientLayer: CAGradientLayer = { - let layer = CAGradientLayer() - layer.anchorPoint = .zero - layer.startPoint = .zero - layer.endPoint = CGPoint(x: 1, y: 0) - - return layer - }() - - /// The progress animation. - /// - /// - Note: `fromValue` and `duration` are updated from the view-model subscription. - private let progressAnimation: CABasicAnimation = { - let animation = CABasicAnimation(keyPath: "position.x") - animation.fromValue = 0 - animation.toValue = 0 - animation.isRemovedOnCompletion = false - animation.repeatCount = Float.infinity - animation.duration = 0 - - return animation - }() - - /// View model containing all logic related to this view. - private let viewModel = GradientActivityIndicatorViewModel() - - /// The dispose bag for the observables. - private var subscriptions = Set() - - // MARK: - Instance Lifecycle - - override public init(frame: CGRect) { - super.init(frame: frame) - - commonInit() - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - - commonInit() - } - - // MARK: - Public methods - - override open func layoutSubviews() { - super.layoutSubviews() - - viewModel.bounds = bounds - } - - override open func point(inside _: CGPoint, with _: UIEvent?) -> Bool { - // Passing all touches to the next view (if any), in the view stack. - false - } - - // MARK: - Private methods - - private func commonInit() { - layer.insertSublayer(gradientLayer, at: 0) - layer.masksToBounds = true - - bindViewModelToView() - } - - private func bindViewModelToView() { - viewModel.isAnimating.sink { [weak self] isAnimating in - self?.updateProgressAnimation(isAnimating: isAnimating) - }.store(in: &subscriptions) - - viewModel.gradientLayerSizeUpdate.sink { [weak self] sizeUpdate in - self?.gradientLayer.frame = sizeUpdate.frame - self?.progressAnimation.fromValue = sizeUpdate.fromValue - }.store(in: &subscriptions) - - viewModel.gradientLayerColors.sink { [weak self] gradientColors in - self?.gradientLayer.colors = gradientColors - }.store(in: &subscriptions) - - viewModel.gradientLayerAnimationDuration.sink { [weak self] animationDuration in - self?.progressAnimation.duration = animationDuration - }.store(in: &subscriptions) - } - - private func updateProgressAnimation(isAnimating: Bool) { - if isAnimating { - gradientLayer.add(progressAnimation, forKey: Config.progressAnimationKey) - } else { - gradientLayer.removeAnimation(forKey: Config.progressAnimationKey) - } - } -} diff --git a/GradientLoadingBar/Sources/Feature/GradientActivityIndicatorView/GradientActivityIndicatorViewModel.swift b/GradientLoadingBar/Sources/Feature/GradientActivityIndicatorView/GradientActivityIndicatorViewModel.swift deleted file mode 100644 index b75f4dc..0000000 --- a/GradientLoadingBar/Sources/Feature/GradientActivityIndicatorView/GradientActivityIndicatorViewModel.swift +++ /dev/null @@ -1,160 +0,0 @@ -// -// GradientActivityIndicatorViewModel.swift -// GradientLoadingBar -// -// Created by Felix Mau on 26.08.19. -// Copyright © 2019 Felix Mau. All rights reserved. -// - -import Combine -import UIKit - -/// This view model contains all logic related to the `GradientActivityIndicatorView`. -final class GradientActivityIndicatorViewModel { - - // MARK: - Public properties - - /// Observable state of the progress animation. - var isAnimating: AnyPublisher { - isAnimatingSubject.eraseToAnyPublisher() - } - - /// Observable frame of the gradient layer. - var gradientLayerSizeUpdate: AnyPublisher { - boundsSubject - .map { SizeUpdate(bounds: $0) } - .eraseToAnyPublisher() - } - - /// Observable color array for the gradient layer (of type `CGColor`). - var gradientLayerColors: AnyPublisher<[CGColor], Never> { - gradientLayerColorsSubject - .map { $0.infiniteGradientColors() } - .map { $0.map(\.cgColor) } - .eraseToAnyPublisher() - } - - /// Observable duration of the animation of the gradient layer. - var gradientLayerAnimationDuration: AnyPublisher { - gradientLayerAnimationDurationSubject.eraseToAnyPublisher() - } - - /// Boolean flag, whether the view is currently hidden. - var isHidden = false { - didSet { - isAnimatingSubject.value = !isHidden - } - } - - /// The bounds of the view. - var bounds: CGRect { - get { boundsSubject.value } - set { boundsSubject.value = newValue } - } - - /// Color array used for the gradient (of type `UIColor`). - var gradientColors: [UIColor] { - get { gradientLayerColorsSubject.value } - set { gradientLayerColorsSubject.value = newValue } - } - - /// The duration for the progress animation. - /// - /// - Note: We explicitly have to pass this value through the view-model, in order to restart the animation when this value changes - /// while the loading bar is visible. - var progressAnimationDuration: TimeInterval { - get { gradientLayerAnimationDurationSubject.value } - set { gradientLayerAnimationDurationSubject.value = newValue } - } - - // MARK: - Private properties - - // As a `UIView` is initially visible, we also have to start the progress animation initially. - private let isAnimatingSubject = CurrentValueSubject(true) - private let boundsSubject = CurrentValueSubject(.zero) - - private let gradientLayerColorsSubject = CurrentValueSubject<[UIColor], Never>(UIColor.GradientLoadingBar.gradientColors) - private let gradientLayerAnimationDurationSubject = CurrentValueSubject(TimeInterval.GradientLoadingBar.progressDuration) - - private var subscriptions = Set() - - // MARK: - Instance Lifecycle - - init() { - gradientLayerAnimationDuration.sink { [weak self] _ in - self?.restartAnimationIfNeeded() - }.store(in: &subscriptions) - - gradientLayerSizeUpdate.sink { [weak self] _ in - self?.restartAnimationIfNeeded() - }.store(in: &subscriptions) - } - - // MARK: - Private methods - - /// Unfortunately the only easy way to update a running `CABasicAnimation`, is to restart it. - /// Mutating a running animation throws an exception! - private func restartAnimationIfNeeded() { - guard isAnimatingSubject.value else { return } - - isAnimatingSubject.value = false - isAnimatingSubject.value = true - } -} - -// MARK: - Supporting Types - -extension GradientActivityIndicatorViewModel { - - /// - Note: The `fromValue` used on the `CABasicAnimation` is dependent on the `frame`, - /// therefore we always update these values together. - struct SizeUpdate: Equatable { - - /// The frame of our gradient layer. - /// This has to be three times the width of the parent bounds in order to apply the normal, reversed and - /// again normal gradient colors to simulate the infinite animation. - var frame: CGRect { - var frame = bounds - frame.size.width *= 3 - - return frame - } - - /// The value to start the animation from. - var fromValue: CGFloat { - bounds.width * -2 - } - - /// The boundaries of the superview. - private let bounds: CGRect - - init(bounds: CGRect) { - self.bounds = bounds - } - } -} - -// MARK: - Helper - -private extension Array where Element == UIColor { - - /// Creates an infinite gradient out of the given colors. - /// - /// Therefore we'll reverse the colors and remove the first and last item - /// to prevent duplicate values at the "inner edges" destroying the infinite look. - /// - /// E.g. for array of [.red, .yellow, .green] - /// we will create [.red, .yellow, .green, .yellow, .red, .yellow, .green] - /// - /// E.g. for array of [.red, .yellow, .green, .blue] - /// we will create [.red, .yellow, .green, .blue, .green, .yellow, .red, .yellow, .green, .blue] - /// - /// With the created array we can animate from left to right and restart the animation without a noticeable glitch. - func infiniteGradientColors() -> [UIColor] { - let reversedColors = reversed() - .dropFirst() - .dropLast() - - return self + reversedColors + self - } -} diff --git a/GradientLoadingBar/Sources/Feature/GradientLoadingBar/GradientLoadingBarController.swift b/GradientLoadingBar/Sources/Feature/GradientLoadingBar/GradientLoadingBarController.swift deleted file mode 100644 index a8eadfb..0000000 --- a/GradientLoadingBar/Sources/Feature/GradientLoadingBar/GradientLoadingBarController.swift +++ /dev/null @@ -1,136 +0,0 @@ -// -// GradientLoadingBarController.swift -// GradientLoadingBar -// -// Created by Felix Mau on 12.11.16. -// Copyright © 2016 Felix Mau. All rights reserved. -// - -import Combine -import UIKit - -/// Type-alias for the controller to match pod name. -public typealias GradientLoadingBar = GradientLoadingBarController - -/// The `GradientLoadingBarController` mediates between the `GradientLoadingBarViewModel` and the corresponding `GradientActivityIndicatorView`. -open class GradientLoadingBarController { - - // MARK: - Public properties - - /// The height of the gradient bar. - /// - /// - Note: This property needs to have a public access level to allow overwriting `setupConstraints()`. - public let height: CGFloat - - /// Flag whether the top layout constraint should respect the `safeAreaLayoutGuide`. - /// - /// - Note: This property needs to have a public access level to allow overwriting `setupConstraints()`. - public let isRelativeToSafeArea: Bool - - /// The view containing the gradient layer. - public let gradientActivityIndicatorView = GradientActivityIndicatorView() - - /// The colors for the gradient. - public var gradientColors: [UIColor] { - get { gradientActivityIndicatorView.gradientColors } - set { gradientActivityIndicatorView.gradientColors = newValue } - } - - /// The duration for the progress animation. - public var progressAnimationDuration: TimeInterval { - get { gradientActivityIndicatorView.progressAnimationDuration } - set { gradientActivityIndicatorView.progressAnimationDuration = newValue } - } - - /// Boolean flag, whether the view is currently hidden. - public var isHidden: Bool { - get { gradientActivityIndicatorView.isHidden } - set { gradientActivityIndicatorView.isHidden = newValue } - } - - /// Singleton instance. - public static var shared = GradientLoadingBar() - - // MARK: - Private properties - - /// View model containing logic for the gradient view. - private let viewModel = GradientLoadingBarViewModel() - - /// The dispose bag for the observables. - private var subscriptions = Set() - - // MARK: - Instance Lifecycle - - /// Creates a new gradient loading bar instance. - /// - /// - Parameters: - /// - height: Height of the gradient bar. - /// - isRelativeToSafeArea: Flag whether the top layout constraint should respect the `safeAreaLayoutGuide`. - /// - /// - Returns: Instance of gradient loading bar. - public init(height: CGFloat = .GradientLoadingBar.height, isRelativeToSafeArea: Bool = true) { - self.height = height - self.isRelativeToSafeArea = isRelativeToSafeArea - - bindViewModelToView() - - // We don't want the view to be visible initially. - isHidden = true - } - - deinit { - gradientActivityIndicatorView.removeFromSuperview() - } - - // MARK: - Public methods - - /// Apply layout constraints for gradient loading view. - open func setupConstraints(superview: UIView) { - let superViewTopAnchor: NSLayoutYAxisAnchor - if #available(iOS 11.0, *), isRelativeToSafeArea { - superViewTopAnchor = superview.safeAreaLayoutGuide.topAnchor - } else { - superViewTopAnchor = superview.topAnchor - } - - NSLayoutConstraint.activate([ - gradientActivityIndicatorView.topAnchor.constraint(equalTo: superViewTopAnchor), - gradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: height), - - gradientActivityIndicatorView.leadingAnchor.constraint(equalTo: superview.leadingAnchor), - gradientActivityIndicatorView.trailingAnchor.constraint(equalTo: superview.trailingAnchor), - ]) - } - - /// Fades in the gradient loading bar. - public func fadeIn(duration: TimeInterval = .GradientLoadingBar.fadeInDuration, completion: ((Bool) -> Void)? = nil) { - gradientActivityIndicatorView.fadeIn(duration: duration, - completion: completion) - } - - /// Fades out the gradient loading bar. - public func fadeOut(duration: TimeInterval = .GradientLoadingBar.fadeOutDuration, completion: ((Bool) -> Void)? = nil) { - gradientActivityIndicatorView.fadeOut(duration: duration, - completion: completion) - } - - // MARK: - Private methods - - private func bindViewModelToView() { - viewModel.superview.sink { [weak self] superview in - self?.updateSuperview(superview) - }.store(in: &subscriptions) - } - - private func updateSuperview(_ superview: UIView?) { - // If the view’s superview is not nil, the superview releases the view. - gradientActivityIndicatorView.removeFromSuperview() - - if let superview = superview { - gradientActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false - superview.addSubview(gradientActivityIndicatorView) - - setupConstraints(superview: superview) - } - } -} diff --git a/GradientLoadingBar/Sources/Feature/GradientLoadingBar/GradientLoadingBarViewModel.swift b/GradientLoadingBar/Sources/Feature/GradientLoadingBar/GradientLoadingBarViewModel.swift deleted file mode 100644 index eae37fe..0000000 --- a/GradientLoadingBar/Sources/Feature/GradientLoadingBar/GradientLoadingBarViewModel.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// GradientLoadingBarViewModel.swift -// GradientLoadingBar -// -// Created by Felix Mau on 26.12.17. -// Copyright © 2017 Felix Mau. All rights reserved. -// - -import Combine -import UIKit - -/// This view model checks for the availability of the key-window, -/// and adds it as a superview to the gradient-view. -final class GradientLoadingBarViewModel { - - // MARK: - Public properties - - /// Observable for the superview of the gradient-view. - var superview: AnyPublisher { - superviewSubject.eraseToAnyPublisher() - } - - // MARK: - Private properties - - private let superviewSubject = CurrentValueSubject(nil) - - private var subscriptions = Set() - - // MARK: - Instance Lifecycle - - init(sharedApplication: UIApplicationProtocol = UIApplication.shared, notificationCenter: NotificationCenter = .default) { - if let keyWindow = sharedApplication.keyWindowInConnectedScenes { - superviewSubject.value = keyWindow - } - - // The key window might be not available yet. This can happen, if the initializer is called from - // `UIApplicationDelegate.application(_:didFinishLaunchingWithOptions:)`. - // Furthermore the key window can change. Therefore we setup an observer to inform the view model - // when a new `UIWindow` object becomes the key window. - notificationCenter - .publisher(for: UIWindow.didBecomeKeyNotification) - .compactMap { _ in sharedApplication.keyWindowInConnectedScenes } - .sink { [weak self] keyWindow in - self?.superviewSubject.value = keyWindow - } - .store(in: &subscriptions) - } - - deinit { - /// By providing a custom de-initializer we make sure to remove the gradient-view from its superview. - superviewSubject.value = nil - } -} - -// MARK: - Helper - -/// This allows mocking `UIApplication` in tests. -protocol UIApplicationProtocol: AnyObject { - var keyWindowInConnectedScenes: UIWindow? { get } -} - -extension UIApplication: UIApplicationProtocol { - /// Returns the current key window across multiple iOS versions. - var keyWindowInConnectedScenes: UIWindow? { - guard #available(iOS 15.0, *) else { - return windows.first { $0.isKeyWindow } - } - - // Starting from iOS 15.0 we need to use `UIWindowScene.windows` on a relevant window scene instead. - // Source: https://stackoverflow.com/a/58031897 - return connectedScenes - .compactMap { $0 as? UIWindowScene } - .flatMap(\.windows) - .first { $0.isKeyWindow } - } -} diff --git a/GradientLoadingBar/Sources/Feature/GradientLoadingBarView/GradientLoadingBarView+ViewModel.swift b/GradientLoadingBar/Sources/Feature/GradientLoadingBarView/GradientLoadingBarView+ViewModel.swift deleted file mode 100644 index f74264e..0000000 --- a/GradientLoadingBar/Sources/Feature/GradientLoadingBarView/GradientLoadingBarView+ViewModel.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// GradientLoadingBarView+ViewModel.swift -// GradientLoadingBar -// -// Created by Felix Mau on 21.03.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import Combine -import SwiftUI - -// See documentation of `GradientLoadingBarView` for further details -// on this compile condition. -#if arch(arm64) || arch(x86_64) - - @available(iOS 15.0, *) - extension GradientLoadingBarView { - /// This view model contains all logic related to the `GradientLoadingBarView` - /// and the corresponding progress animation. - final class ViewModel: ObservableObject { - - // MARK: - Public properties - - /// The gradient colors used for the progress animation (including the reversed colors). - let gradientColors: [Color] - - /// The horizontal offset of the `LinearGradient` used to simulate the progress animation. - @Published - private(set) var horizontalOffset: CGFloat = 0 - - /// The current size of the `GradientLoadingBarView`. - @Published - var size: CGSize = .zero { - didSet { - // This will stop any ongoing animation. - // Source: https://stackoverflow.com/a/59150940 - withAnimation(.linear(duration: 0)) { - horizontalOffset = -size.width - } - - let progressAnimation: Animation = .linear(duration: progressDuration).repeatForever(autoreverses: false) - withAnimation(progressAnimation) { - horizontalOffset = size.width - } - } - } - - /// The width of the `LinearGradient`. - var gradientWidth: CGFloat { - // To fit `gradientColors + reversedGradientColors + gradientColors` in our view, - // we have to apply three times the width of our parent view. - size.width * 3 - } - - // MARK: - Private properties - - private var progressDuration: TimeInterval - - // MARK: - Instance Lifecycle - - init(gradientColors: [Color], progressDuration: TimeInterval) { - // Simulate infinite animation - Therefore we'll reverse the colors and remove the first and last item - // to prevent duplicate values at the "inner edges" destroying the infinite look. - // - // E.g. for array of [.red, .yellow, .green] - // we will create [.red, .yellow, .green, .yellow, .red, .yellow, .green] - // - // E.g. for array of [.red, .yellow, .green, .blue] - // we will create [.red, .yellow, .green, .blue, .green, .yellow, .red, .yellow, .green, .blue] - let reversedGradientColors = gradientColors - .reversed() - .dropFirst() - .dropLast() - - self.gradientColors = gradientColors + reversedGradientColors + gradientColors - self.progressDuration = progressDuration - } - } - } - -#endif diff --git a/GradientLoadingBar/Sources/Feature/GradientLoadingBarView/GradientLoadingBarView.swift b/GradientLoadingBar/Sources/Feature/GradientLoadingBarView/GradientLoadingBarView.swift deleted file mode 100644 index 9fdb27b..0000000 --- a/GradientLoadingBar/Sources/Feature/GradientLoadingBarView/GradientLoadingBarView.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// GradientLoadingBarView.swift -// GradientLoadingBar -// -// Created by Felix Mau on 08.03.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import SwiftUI - -// Workaround to fix `xcodebuild` errors when running `pod lib lint`, like e.g.: -// -// - `xcodebuild`: error: cannot find type 'Color' in scope -// - `xcodebuild`: error: cannot find type 'View' in scope -// -// > This failure occurs because builds with a deployment target earlier than iOS 11 will also build for the armv7 architecture, -// > and there is no armv7 swiftmodule for SwiftUI in the iOS SDK because the OS version in which it was first introduced (iOS 13) -// > does not support armv7 anymore. -// -// Source: https://stackoverflow.com/a/61954608 -#if arch(arm64) || arch(x86_64) - - // For some reason the animation looks broken on iOS versions <= 15.0. - @available(iOS 15.0, *) - public struct GradientLoadingBarView: View { - - // MARK: - Config - - public enum Config { - /// The default color palette for the gradient colors. - public static let gradientColors = UIColor.GradientLoadingBar.gradientColors.map(Color.init) - - /// The default duration for the progress animation, measured in seconds. - public static let progressDuration = TimeInterval.GradientLoadingBar.progressDuration - } - - // MARK: - Private properties - - @StateObject - private var viewModel: ViewModel - - // MARK: - Initializer - - public init(gradientColors: [Color] = Config.gradientColors, - progressDuration: TimeInterval = Config.progressDuration) { - // Even though the docs mention that "You don’t call this initializer directly", this seems to be the correct way to set-up a - // `StateObject` with parameters according to "Lessons from the SwiftUI Digital Lounge". - // https://swiftui-lab.com/random-lessons/#data-10 - _viewModel = StateObject( - wrappedValue: ViewModel(gradientColors: gradientColors, progressDuration: progressDuration) - ) - } - - // MARK: - Render - - public var body: some View { - Color.clear - // We explicitly have to use a `PreferenceKey` here and store the size on a property in order to restart the animation whenever - // the size changes. Using a `GeometryReader` together with the `onAppear(_:)` view-modifier doesn't reflect any size changes. - .modifier(SizeModifier()) - .onPreferenceChange(SizePreferenceKey.self) { - viewModel.size = $0 - } - // Using an `overlay` here makes sure that the parent view won't change it's frame. - .overlay( - LinearGradient(colors: viewModel.gradientColors, startPoint: .leading, endPoint: .trailing) - .frame(width: viewModel.gradientWidth) - .offset(x: viewModel.horizontalOffset, y: 0) - ) - } - } - - // MARK: - Helper - - private struct SizePreferenceKey: PreferenceKey { - static var defaultValue: CGSize = .zero - - static func reduce(value: inout CGSize, nextValue: () -> CGSize) { - value = nextValue() - } - } - - @available(iOS 15.0, *) - private struct SizeModifier: ViewModifier { - func body(content: Content) -> some View { - content.background( - GeometryReader { geometry in - Color.clear.preference(key: SizePreferenceKey.self, - value: geometry.size) - } - ) - } - } - -#endif diff --git a/GradientLoadingBar/Sources/Feature/NotchGradientLoadingBar/NotchGradientLoadingBarController.swift b/GradientLoadingBar/Sources/Feature/NotchGradientLoadingBar/NotchGradientLoadingBarController.swift deleted file mode 100644 index df33f76..0000000 --- a/GradientLoadingBar/Sources/Feature/NotchGradientLoadingBar/NotchGradientLoadingBarController.swift +++ /dev/null @@ -1,275 +0,0 @@ -// -// NotchGradientLoadingBarController.swift -// GradientLoadingBar -// -// Created by Felix Mau on 06.11.20. -// Copyright © 2020 Felix Mau. All rights reserved. -// - -import UIKit - -/// Type-alias for the controller to be more similar to the pod name. -public typealias NotchGradientLoadingBar = NotchGradientLoadingBarController - -open class NotchGradientLoadingBarController: GradientLoadingBarController { - - // MARK: - Private properties - - private let viewModel = NotchGradientLoadingBarViewModel() - - // MARK: - Public methods - - override open func setupConstraints(superview: UIView) { - guard let notchDevice = viewModel.notchDevice else { - // No special masking required for devices without a notch. - super.setupConstraints(superview: superview) - return - } - - // We currently only support portrait mode (without device rotation), - // and therefore can safely use `bounds.size.width` here. - let screenWidth = superview.bounds.size.width - - let notchConfig = NotchConfig(notchDevice: notchDevice) - let notchBezierPath = notchBezierPath(for: screenWidth, notchConfig: notchConfig) - - let viewHeight = notchBezierPath.bounds.height + 1 - NSLayoutConstraint.activate([ - gradientActivityIndicatorView.topAnchor.constraint(equalTo: superview.topAnchor), - gradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: viewHeight), - - gradientActivityIndicatorView.leadingAnchor.constraint(equalTo: superview.leadingAnchor), - gradientActivityIndicatorView.trailingAnchor.constraint(equalTo: superview.trailingAnchor), - ]) - - maskView(with: notchBezierPath) - } - - // MARK: - Private methods - - // swiftlint:disable:next function_body_length - private func notchBezierPath(for screenWidth: CGFloat, notchConfig: NotchConfig) -> UIBezierPath { - // We always center the notch in the middle of the screen. - // - // Please have a look at the graphic `Assets/iphone-x-screen-demystified.svg` or the entire article at - // https://www.paintcodeapp.com/news/iphone-x-screen-demystified for further details on the notch layout. - // - // In the graphic the `leftNotchPoint` lays `83pt` from the left side of the device and - // the `rightNotchPoint` lays `83pt` from the right. - let leftNotchPoint = (screenWidth - notchConfig.notchWidth) / 2 - let rightNotchPoint = (screenWidth + notchConfig.notchWidth) / 2 - - // The center point of the large circles lays at the bottom of the small circles. - let smallCircleDiameter: CGFloat = 2 * notchConfig.smallCircleRadius - - // Reducing the height here a little in order to match the "basic" gradient loading bar. - let height = height - 0.5 - - let bezierPath = UIBezierPath() - bezierPath.move(to: .zero) - - // Draw line to small-circle left to `leftNotchPoint`. - bezierPath.addLineTo(x: leftNotchPoint - notchConfig.smallCircleRadius, - y: 0) - - // Draw the small circle left to the `leftNotchPoint`. - // See for the definition of the - // angles in the default coordinate system. - bezierPath.addArc(withCenter: CGPoint(x: leftNotchPoint - notchConfig.smallCircleRadius, - y: notchConfig.smallCircleRadius), - radius: notchConfig.smallCircleRadius, - startAngle: -CGFloat.pi / 2, - endAngle: 0, - clockwise: true) - - // Draw the large circle right to the `leftNotchPoint`. - bezierPath.addArc(withCenter: CGPoint(x: leftNotchPoint + notchConfig.largeCircleRadius, - y: smallCircleDiameter + notchConfig.largeCircleVerticalOffset), - radius: notchConfig.largeCircleRadius, - startAngle: CGFloat.pi, - endAngle: CGFloat.pi / 2, - clockwise: false) - - // Draw line to large-circle underneath and left to `rightNotchPoint`. - bezierPath.addLineTo(x: rightNotchPoint - notchConfig.largeCircleRadius, - y: smallCircleDiameter + notchConfig.largeCircleRadius + notchConfig.largeCircleVerticalOffset) - - // Draw the large circle left to the `rightNotchPoint`. - bezierPath.addArc(withCenter: CGPoint(x: rightNotchPoint - notchConfig.largeCircleRadius, - y: smallCircleDiameter + notchConfig.largeCircleVerticalOffset), - radius: notchConfig.largeCircleRadius, - startAngle: CGFloat.pi / 2, - endAngle: 0, - clockwise: false) - - // Draw the small circle right to the `rightNotchPoint`. - bezierPath.addArc(withCenter: CGPoint(x: rightNotchPoint + notchConfig.smallCircleRadius, - y: notchConfig.smallCircleRadius), - radius: notchConfig.smallCircleRadius, - startAngle: CGFloat.pi, - endAngle: CGFloat.pi + CGFloat.pi / 2, - clockwise: true) - - // Draw line to the end of the screen. - bezierPath.addLineTo(x: screenWidth, y: 0) - - // And all the way back.. - // - // For the circles on the way back we use the same center point as in the beginning but adapt the radius. - // See also: https://twitter.com/lilykonings/status/1567317037126680576?s=46&t=Cm2Q8BsqSY_nbCrZqGE08g - - // Draw line down. - bezierPath.addLineTo(x: screenWidth, - y: height) - - // Draw line to small-circle underneath and right to `rightNotchPoint`. - bezierPath.addLineTo(x: height + rightNotchPoint + notchConfig.smallCircleRadius, - y: height) - - // Draw the small circle right to the `rightNotchPoint`. - bezierPath.addArc(withCenter: CGPoint(x: rightNotchPoint + notchConfig.smallCircleRadius, - y: notchConfig.smallCircleRadius), - radius: notchConfig.smallCircleRadius - height, - startAngle: CGFloat.pi + CGFloat.pi / 2, - endAngle: CGFloat.pi, - clockwise: false) - - // Draw the large circle left to the `rightNotchPoint`. - bezierPath.addArc(withCenter: CGPoint(x: rightNotchPoint - notchConfig.largeCircleRadius, - y: smallCircleDiameter + notchConfig.largeCircleVerticalOffset), - radius: notchConfig.largeCircleRadius + height, - startAngle: 0, - endAngle: CGFloat.pi / 2, - clockwise: true) - - // Draw line to large-circle underneath and right to `leftNotchPoint` - bezierPath.addLineTo(x: height + leftNotchPoint + notchConfig.largeCircleRadius, - y: height + smallCircleDiameter + notchConfig.largeCircleRadius + notchConfig.largeCircleVerticalOffset) - - // Draw the large circle right to the `leftNotchPoint`. - bezierPath.addArc(withCenter: CGPoint(x: leftNotchPoint + notchConfig.largeCircleRadius, - y: smallCircleDiameter + notchConfig.largeCircleVerticalOffset), - radius: notchConfig.largeCircleRadius + height, - startAngle: CGFloat.pi / 2, - endAngle: CGFloat.pi, - clockwise: true) - - // Draw the small circle left to the `leftNotchPoint`. - bezierPath.addArc(withCenter: CGPoint(x: leftNotchPoint - notchConfig.smallCircleRadius, - y: notchConfig.smallCircleRadius), - radius: notchConfig.smallCircleRadius - height, - startAngle: 0, - endAngle: -CGFloat.pi / 2, - clockwise: false) - - // Draw line to the beginning of the screen. - bezierPath.addLineTo(x: 0, y: height) - bezierPath.close() - - bezierPath.apply(notchConfig.transform) - - return bezierPath - } - - private func maskView(with bezierPath: UIBezierPath) { - let shapeLayer = CAShapeLayer() - shapeLayer.path = bezierPath.cgPath - shapeLayer.strokeColor = UIColor.white.cgColor - - if #available(iOS 13.0, *) { - shapeLayer.cornerCurve = .continuous - } - - gradientActivityIndicatorView.layer.mask = shapeLayer - } -} - -// MARK: - Supporting Types - -private struct NotchConfig { - /// The width of the iPhone notch. - let notchWidth: CGFloat - - /// The radius of the small circle on the outside of the notch. - let smallCircleRadius: CGFloat = 6 - - /// The radius of the large circle on the inside of the notch. - let largeCircleRadius: CGFloat - - /// Vertical offset for the center-point of the large circle: - /// - A positive value will move the large circles downwards. - /// - A negative offset will move them upwards. - let largeCircleVerticalOffset: CGFloat - - /// The transform to be applied to the entire bezier path. - let transform: CGAffineTransform -} - -private extension NotchConfig { - - /// Initializes the notch specific configuration for the current safe area device. - /// - /// - Note: We define this in an extension to keep the memberwise initializer. - init(notchDevice: NotchGradientLoadingBarViewModel.NotchDevice) { - switch notchDevice { - case .iPhoneX, .iPhoneXS, .iPhoneXSMax: - /// The default configuration for the iPhone X. - /// Values are based on . - self.init(notchWidth: 209, - largeCircleRadius: 22.5, - largeCircleVerticalOffset: -4.75, - transform: notchDevice == .iPhoneXSMax ? .identity : CGAffineTransform(translationX: 0.33, y: 0)) - - case .iPhoneXR, .iPhone11: - self.init(notchWidth: 230, - largeCircleRadius: 24, - largeCircleVerticalOffset: -3.5, - transform: .identity) - - // The "iPhone 11 Pro" and "iPhone 11 Pro Max" have a smaller notch than the "iPhone 11". - case .iPhone11Pro, .iPhone11ProMax: - self.init(notchWidth: 209, - largeCircleRadius: 21, - largeCircleVerticalOffset: -3.5, - transform: notchDevice == .iPhone11ProMax ? .identity : CGAffineTransform(translationX: 0.33, y: 0)) - - // The "iPhone 12 Mini" has a larger notch than the "iPhone 12". - case .iPhone12Mini: - self.init(notchWidth: 226, - largeCircleRadius: 24, - largeCircleVerticalOffset: -2, - transform: .identity) - - case .iPhone12, .iPhone12Pro, .iPhone12ProMax: - self.init(notchWidth: 209.5, - largeCircleRadius: 21, - largeCircleVerticalOffset: -1.75, - transform: .identity) - - // The "iPhone 13 Mini" has a larger notch than the "iPhone 13". - case .iPhone13Mini: - self.init(notchWidth: 174.75, - largeCircleRadius: 24.5, - largeCircleVerticalOffset: 0.5, - transform: .identity) - - case .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14, .iPhone14Plus: - // The iPhone 13 specific configuration: ‟iPhone 13 notch is 20% smaller in width, but it is also a little taller in height‟. - // Source: . - self.init(notchWidth: 161, - largeCircleRadius: 22, - largeCircleVerticalOffset: -1, - transform: .identity) - } - } -} - -// MARK: - Helper - -private extension UIBezierPath { - - // swiftlint:disable:next identifier_name - func addLineTo(x: CGFloat, y: CGFloat) { - addLine(to: CGPoint(x: x, y: y)) - } -} diff --git a/GradientLoadingBar/Sources/Feature/NotchGradientLoadingBar/NotchGradientLoadingBarViewModel.swift b/GradientLoadingBar/Sources/Feature/NotchGradientLoadingBar/NotchGradientLoadingBarViewModel.swift deleted file mode 100644 index 4d94dc3..0000000 --- a/GradientLoadingBar/Sources/Feature/NotchGradientLoadingBar/NotchGradientLoadingBarViewModel.swift +++ /dev/null @@ -1,137 +0,0 @@ -// -// NotchGradientLoadingBarViewModel.swift -// GradientLoadingBar -// -// Created by Felix Mau on 03.10.21. -// Copyright © 2021 Felix Mau. All rights reserved. -// - -import UIKit - -final class NotchGradientLoadingBarViewModel { - - // MARK: - Types - - enum NotchDevice { - case iPhoneX - case iPhoneXS - case iPhoneXSMax - case iPhoneXR - case iPhone11 - case iPhone11Pro - case iPhone11ProMax - case iPhone12Mini - case iPhone12 - case iPhone12Pro - case iPhone12ProMax - case iPhone13Mini - case iPhone13 - case iPhone13Pro - case iPhone13ProMax - case iPhone14 - case iPhone14Plus - } - - // MARK: - Public properties - - /// The current device if it has a notch / otherwise `nil`. - let notchDevice: NotchDevice? - - // MARK: - Instance Lifecycle - - init(deviceIdentifier: String = UIDevice.identifier) { - notchDevice = NotchDevice(deviceIdentifier: deviceIdentifier) - } -} - -// MARK: - Helper - -private extension NotchGradientLoadingBarViewModel.NotchDevice { - - /// Creates a new instance from a given `deviceIdentifier` (value returned by `UIDevice.identifier`). - /// - /// - Note: This is taken from - init?(deviceIdentifier: String) { - // swiftlint:disable:previous cyclomatic_complexity - switch deviceIdentifier { - case "iPhone10,3", "iPhone10,6": - self = .iPhoneX - - case "iPhone11,2": - self = .iPhoneXS - - case "iPhone11,4", "iPhone11,6": - self = .iPhoneXSMax - - case "iPhone11,8": - self = .iPhoneXR - - case "iPhone12,1": - self = .iPhone11 - - case "iPhone12,3": - self = .iPhone11Pro - - case "iPhone12,5": - self = .iPhone11ProMax - - case "iPhone13,1": - self = .iPhone12Mini - - case "iPhone13,2": - self = .iPhone12 - - case "iPhone13,3": - self = .iPhone12Pro - - case "iPhone13,4": - self = .iPhone12ProMax - - case "iPhone14,4": - self = .iPhone13Mini - - case "iPhone14,5": - self = .iPhone13 - - case "iPhone14,2": - self = .iPhone13Pro - - case "iPhone14,3": - self = .iPhone13ProMax - - case "iPhone14,7": - self = .iPhone14 - - case "iPhone14,8": - self = .iPhone14Plus - - default: - return nil - } - } -} - -private extension UIDevice { - - /// Returns the device identifier. - /// - /// Based on: - /// Adapted for Simulator usage based on - static var identifier: String { - #if targetEnvironment(simulator) - return ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "" - #else - var systemInfo = utsname() - uname(&systemInfo) - - let machineMirror = Mirror(reflecting: systemInfo.machine) - return machineMirror.children.reduce("") { identifier, element in - guard let value = element.value as? Int8, value != 0 else { - return identifier - } - - return identifier + String(UnicodeScalar(UInt8(value))) - } - #endif - } -} diff --git a/GradientLoadingBar/Sources/Helper/Constants.swift b/GradientLoadingBar/Sources/Helper/Constants.swift deleted file mode 100644 index d578d1e..0000000 --- a/GradientLoadingBar/Sources/Helper/Constants.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Constants.swift -// GradientLoadingBar -// -// Created by Felix Mau on 26.08.19. -// Copyright © 2019 Felix Mau. All rights reserved. -// - -import UIKit - -public extension UIColor { - - // swiftlint:disable:next missing_docs - enum GradientLoadingBar { - /// The default color palette for the gradient colors. - /// - /// - SeeAlso: https://codepen.io/marcobiedermann/pen/LExXWW - public static let gradientColors = [ - #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), - ] - } -} - -public extension CGFloat { - - // swiftlint:disable:next missing_docs - enum GradientLoadingBar { - /// The default height of the `GradientLoadingBar`. - public static let height: CGFloat = 3 - } -} - -public extension TimeInterval { - - // swiftlint:disable:next missing_docs - enum GradientLoadingBar { - /// The default duration for fading-in the loading bar, measured in seconds. - public static let fadeInDuration = 0.33 - - /// The default duration for fading-out the loading bar, measured in seconds. - public static let fadeOutDuration = 0.66 - - /// The default duration for the progress animation, measured in seconds. - public static let progressDuration = 3.33 - } -} diff --git a/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/GradientActivityIndicatorViewTestCase.swift b/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/GradientActivityIndicatorViewTestCase.swift deleted file mode 100644 index 0222523..0000000 --- a/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/GradientActivityIndicatorViewTestCase.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// GradientActivityIndicatorViewTestCase.swift -// ExampleSnapshotTests -// -// Created by Felix Mau on 09.11.19. -// Copyright © 2019 Felix Mau. All rights reserved. -// - -import SnapshotTesting -import XCTest - -@testable import GradientLoadingBar - -final class GradientActivityIndicatorViewTestCase: XCTestCase { - - // MARK: - Config - - private enum Config { - /// The frame we use for rendering the `GradientActivityIndicatorView`. - /// This will also be the image size for our snapshot. - static let frame = CGRect(origin: .zero, size: CGSize(width: 375, height: 4)) - - /// The custom colors we use on this test-case. - /// Source: https://color.adobe.com/Pink-Flamingo-color-theme-10343714/ - static let gradientColors = [ - #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), - ] - } - - // MARK: - Test cases - - func test_gradientActivityIndicatorView_shouldContainCorrectDefaultColors() { - // Given - let gradientActivityIndicatorView = GradientActivityIndicatorView(frame: Config.frame) - - // We're only interested in the initial state containing the correct gradient-colors. - // Therefore we're gonna remove the progress animation. - gradientActivityIndicatorView.layer.removeAllAnimations() - - // Then - assertSnapshot(matching: gradientActivityIndicatorView, as: .image) - } - - func test_gradientActivityIndicatorView_shouldContainCorrectCustomColors() { - // Given - let gradientActivityIndicatorView = GradientActivityIndicatorView(frame: Config.frame) - gradientActivityIndicatorView.gradientColors = Config.gradientColors - - // We're only interested in the initial state containing the correct gradient-colors. - // Therefore we're gonna remove the progress animation. - gradientActivityIndicatorView.layer.removeAllAnimations() - - // Then - assertSnapshot(matching: gradientActivityIndicatorView, as: .image) - } -} diff --git a/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/__Snapshots__/GradientActivityIndicatorViewTestCase/test_gradientActivityIndicatorView_shouldContainCorrectDefaultColors.1.png b/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/__Snapshots__/GradientActivityIndicatorViewTestCase/test_gradientActivityIndicatorView_shouldContainCorrectDefaultColors.1.png deleted file mode 100644 index edec8ec..0000000 Binary files a/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/__Snapshots__/GradientActivityIndicatorViewTestCase/test_gradientActivityIndicatorView_shouldContainCorrectDefaultColors.1.png and /dev/null differ diff --git a/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBar/GradientLoadingBarControllerTestCase.swift b/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBar/GradientLoadingBarControllerTestCase.swift deleted file mode 100644 index 4ec86e7..0000000 --- a/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBar/GradientLoadingBarControllerTestCase.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// GradientLoadingBarControllerTestCase.swift -// ExampleSnapshotTests -// -// Created by Felix Mau on 14.06.20. -// Copyright © 2020 Felix Mau. All rights reserved. -// - -import SnapshotTesting -import XCTest - -@testable import GradientLoadingBar - -final class GradientLoadingBarControllerTestCase: XCTestCase { - - // MARK: - Private properties - - private var window: UIWindow! - - // MARK: - Public methods - - override func setUp() { - super.setUp() - - window = UIApplication.shared.keyWindowInConnectedScenes - } - - override func tearDown() { - window = nil - - super.tearDown() - } - - // MARK: - Test cases - - func test_gradientLoadingBarController() { - // Given - // Show an empty view controller behind loading bar in our test. - let rootViewController = UIViewController() - window.rootViewController = rootViewController - - let expectation = expectation(description: "Expect view to be completely visible.") - - // When - let gradientLoadingBarController = GradientLoadingBarController() - gradientLoadingBarController.fadeIn(duration: 0) { _ in - expectation.fulfill() - } - - // Then - wait(for: [expectation], timeout: 0.1) - - // Workaround, as snapshotting a `UIWindow` is currently not supported and crashes when running all tests. - assertSnapshot(matching: window.layer, as: .image) - } -} diff --git a/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBar/__Snapshots__/GradientLoadingBarControllerTestCase/test_gradientLoadingBarController.1.png b/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBar/__Snapshots__/GradientLoadingBarControllerTestCase/test_gradientLoadingBarController.1.png deleted file mode 100644 index 671fe28..0000000 Binary files a/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBar/__Snapshots__/GradientLoadingBarControllerTestCase/test_gradientLoadingBarController.1.png and /dev/null differ diff --git a/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/GradientLoadingBarViewTestCase.swift b/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/GradientLoadingBarViewTestCase.swift deleted file mode 100644 index 7de411c..0000000 --- a/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/GradientLoadingBarViewTestCase.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// GradientLoadingBarViewTestCase.swift -// ExampleSnapshotTests -// -// Created by Felix Mau on 10.03.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import SnapshotTesting -import SwiftUI -import XCTest - -@testable import GradientLoadingBar - -@available(iOS 15.0, *) -final class GradientLoadingBarViewTestCase: XCTestCase { - - // MARK: - Config - - private enum Config { - /// The frame we use for rendering the `GradientLoadingBarView`. - /// This will also be the image size for our snapshot. - static let frame = CGRect(origin: .zero, size: CGSize(width: 375, height: 4)) - - /// The custom colors we use on this test-case. - /// Source: https://color.adobe.com/Pink-Flamingo-color-theme-10343714/ - static let gradientColors = [ - #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), - ].map(Color.init) - - /// The percentage of pixels that must match. - static let precision: Float = 0.98 - } - - // MARK: - Test cases - - func test_gradientLoadingBarView_shouldContainCorrectDefaultColors() { - // Given - let gradientLoadingBarView = GradientLoadingBarView() - .frame(width: Config.frame.width, height: Config.frame.height) - - // Then - assertSnapshot(matching: gradientLoadingBarView, as: .image(precision: Config.precision)) - } - - func test_gradientLoadingBarView_shouldContainCorrectCustomColors() { - // Given - let gradientLoadingBarView = GradientLoadingBarView(gradientColors: Config.gradientColors) - .frame(width: Config.frame.width, height: Config.frame.height) - - // Then - assertSnapshot(matching: gradientLoadingBarView, as: .image(precision: Config.precision)) - } -} diff --git a/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/__Snapshots__/GradientLoadingBarViewTestCase/test_gradientLoadingBarView_shouldContainCorrectDefaultColors.1.png b/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/__Snapshots__/GradientLoadingBarViewTestCase/test_gradientLoadingBarView_shouldContainCorrectDefaultColors.1.png deleted file mode 100644 index b7d71ce..0000000 Binary files a/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/__Snapshots__/GradientLoadingBarViewTestCase/test_gradientLoadingBarView_shouldContainCorrectDefaultColors.1.png and /dev/null differ diff --git a/GradientLoadingBar/Tests/SnapshotTests/NotchGradientLoadingBar/NotchGradientLoadingBarControllerTestCase.swift b/GradientLoadingBar/Tests/SnapshotTests/NotchGradientLoadingBar/NotchGradientLoadingBarControllerTestCase.swift deleted file mode 100644 index 1071feb..0000000 --- a/GradientLoadingBar/Tests/SnapshotTests/NotchGradientLoadingBar/NotchGradientLoadingBarControllerTestCase.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// NotchGradientLoadingBarControllerTestCase.swift -// ExampleSnapshotTests -// -// Created by Felix Mau on 14.06.20. -// Copyright © 2020 Felix Mau. All rights reserved. -// - -import SnapshotTesting -import XCTest - -@testable import GradientLoadingBar - -final class NotchGradientLoadingBarControllerTestCase: XCTestCase { - // swiftlint:disable:previous type_name - - // MARK: - Private properties - - private var window: UIWindow! - - // MARK: - Public methods - - override func setUp() { - super.setUp() - - window = UIApplication.shared.keyWindowInConnectedScenes - } - - override func tearDown() { - window = nil - - super.tearDown() - } - - // MARK: - Test cases - - func test_notchGradientLoadingBarController() { - // Given - // Show an empty view controller behind loading bar in our test. - let rootViewController = UIViewController() - window.rootViewController = rootViewController - - let expectation = expectation(description: "Expect view to be completely visible.") - - // When - let gradientLoadingBarController = NotchGradientLoadingBarController() - gradientLoadingBarController.fadeIn(duration: 0) { _ in - expectation.fulfill() - } - - // Then - wait(for: [expectation], timeout: 0.1) - - // Workaround, as snapshotting a `UIWindow` is currently not supported and crashes when running all tests. - assertSnapshot(matching: window.layer, as: .image) - } -} diff --git a/GradientLoadingBar/Tests/SnapshotTests/NotchGradientLoadingBar/__Snapshots__/NotchGradientLoadingBarControllerTestCase/test_notchGradientLoadingBarController.1.png b/GradientLoadingBar/Tests/SnapshotTests/NotchGradientLoadingBar/__Snapshots__/NotchGradientLoadingBarControllerTestCase/test_notchGradientLoadingBarController.1.png deleted file mode 100644 index 38bf349..0000000 Binary files a/GradientLoadingBar/Tests/SnapshotTests/NotchGradientLoadingBar/__Snapshots__/NotchGradientLoadingBarControllerTestCase/test_notchGradientLoadingBarController.1.png and /dev/null differ diff --git a/GradientLoadingBar/Tests/SnapshotTests/README.md b/GradientLoadingBar/Tests/SnapshotTests/README.md deleted file mode 100644 index 1da8c5e..0000000 --- a/GradientLoadingBar/Tests/SnapshotTests/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Snapshot-Tests - -## Device -Run test-cases with the `iPhone 14` using `iOS 16`. - -- Note: We explicitly use this iPhone to verify the `NotchGradientLoadingBar`. diff --git a/GradientLoadingBar/Tests/UnitTests/GradientActivityIndicatorView/GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift b/GradientLoadingBar/Tests/UnitTests/GradientActivityIndicatorView/GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift deleted file mode 100644 index fe5fed9..0000000 --- a/GradientLoadingBar/Tests/UnitTests/GradientActivityIndicatorView/GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift +++ /dev/null @@ -1,208 +0,0 @@ -// -// GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift -// ExampleTests -// -// Created by Felix Mau on 19.05.19. -// Copyright © 2017 Felix Mau. All rights reserved. -// - -import XCTest - -@testable import GradientLoadingBar - -// swiftlint:disable:next type_name -final class GradientActivityIndicatorView_AnimateIsHiddenTestCase: XCTestCase { - - // MARK: - Private properties - - private var window: UIWindow! - private var gradientActivityIndicatorView: GradientActivityIndicatorView! - - // MARK: - Public methods - - override func setUp() { - super.setUp() - - // In order for UIView animations to be executed correctly, the corresponding view has to be attached to a visible window. - // Therefore we're gonna use the current key-window, add our testing view here in `setUp()` and remove it later in `tearDown()`. - window = UIApplication.shared.keyWindowInConnectedScenes - - gradientActivityIndicatorView = GradientActivityIndicatorView() - window.addSubview(gradientActivityIndicatorView) - } - - override func tearDown() { - gradientActivityIndicatorView.removeFromSuperview() - gradientActivityIndicatorView = nil - - window = nil - - super.tearDown() - } - - // MARK: - Test method `animate(isHidden:)` - - func test_animateIsHidden_shouldShowView_andCallCompletionHandler() { - // Given - let expectation = expectation(description: "Expect completion handler to be invoked.") - - // Hide view to validate fade-in. - gradientActivityIndicatorView.alpha = 0 - gradientActivityIndicatorView.isHidden = true - - // When - gradientActivityIndicatorView.animate(isHidden: false, duration: 0.1) { isFinished in - XCTAssertTrue(isFinished) - expectation.fulfill() - } - - // Then - wait(for: [expectation], timeout: 1) - - XCTAssertFalse(gradientActivityIndicatorView.isHidden) - XCTAssertEqual(gradientActivityIndicatorView.alpha, 1, accuracy: CGFloat.ulpOfOne) - } - - func test_animateIsHidden_withInterruption_shouldShowView_andCallCompletionHandler() { - // Given - let expectation = expectation(description: "Expect completion handler to be invoked.") - - // Hide view to validate fade-in. - gradientActivityIndicatorView.alpha = 0 - gradientActivityIndicatorView.isHidden = true - - // When - gradientActivityIndicatorView.animate(isHidden: false, duration: 0.1) { isFinished in - XCTAssertFalse(isFinished) - expectation.fulfill() - } - - // Cancel animation. - gradientActivityIndicatorView.layer.removeAllAnimations() - - // Then - wait(for: [expectation], timeout: 1) - - XCTAssertFalse(gradientActivityIndicatorView.isHidden) - } - - func test_animateIsHidden_shouldHideView_andCallCompletionHandler() { - // Given - let expectation = expectation(description: "Expect completion handler to be invoked.") - - // When - gradientActivityIndicatorView.animate(isHidden: true, duration: 0.1) { isFinished in - XCTAssertTrue(isFinished) - expectation.fulfill() - } - - // Then - wait(for: [expectation], timeout: 1) - - XCTAssertTrue(gradientActivityIndicatorView.isHidden) - XCTAssertEqual(gradientActivityIndicatorView.alpha, 0, accuracy: CGFloat.ulpOfOne) - } - - func test_animateIsHidden_withInterruption_shouldNotHideView_andCallCompletionHandler() { - // Given - let expectation = expectation(description: "Expect completion handler to be invoked.") - - // When - gradientActivityIndicatorView.animate(isHidden: true, duration: 0.1) { isFinished in - XCTAssertFalse(isFinished) - expectation.fulfill() - } - - // Cancel animation. - gradientActivityIndicatorView.layer.removeAllAnimations() - - // Then - wait(for: [expectation], timeout: 1) - - XCTAssertFalse(gradientActivityIndicatorView.isHidden, "As we've interrupted the animation, we expect the `isHidden` flag to still be `false`.") - } - - // MARK: - Test method `fadeIn()` - - func test_fadeIn_shouldShowView_andCallCompletionHandler() { - // Given - let expectation = expectation(description: "Expect completion handler to be invoked.") - - // Hide view to validate fade-in. - gradientActivityIndicatorView.alpha = 0 - gradientActivityIndicatorView.isHidden = true - - // When - gradientActivityIndicatorView.fadeIn(duration: 0.1) { isFinished in - XCTAssertTrue(isFinished) - expectation.fulfill() - } - - // Then - wait(for: [expectation], timeout: 1) - - XCTAssertFalse(gradientActivityIndicatorView.isHidden) - XCTAssertEqual(gradientActivityIndicatorView.alpha, 1, accuracy: CGFloat.ulpOfOne) - } - - func test_fadeIn_withInterruption_shouldShowView_andCallCompletionHandler() { - // Given - let expectation = expectation(description: "Expect completion handler to be invoked.") - - // Hide view to validate fade-in. - gradientActivityIndicatorView.alpha = 0 - gradientActivityIndicatorView.isHidden = true - - // When - gradientActivityIndicatorView.fadeIn(duration: 0.1) { isFinished in - XCTAssertFalse(isFinished) - expectation.fulfill() - } - - // Cancel animation. - gradientActivityIndicatorView.layer.removeAllAnimations() - - // Then - wait(for: [expectation], timeout: 1) - - XCTAssertFalse(gradientActivityIndicatorView.isHidden) - } - - // MARK: - Test method `fadeOut()` - - func test_fadeOut_shouldHideView_andCallCompletionHandler() { - // Given - let expectation = expectation(description: "Expect completion handler to be invoked.") - - // When - gradientActivityIndicatorView.fadeOut(duration: 0.1) { isFinished in - XCTAssertTrue(isFinished) - expectation.fulfill() - } - - // Then - wait(for: [expectation], timeout: 1) - - XCTAssertTrue(gradientActivityIndicatorView.isHidden) - XCTAssertEqual(gradientActivityIndicatorView.alpha, 0, accuracy: CGFloat.ulpOfOne) - } - - func test_fadeOut_withInterruption_shouldNotHideView_andCallCompletionHandler() { - // Given - let expectation = expectation(description: "Expect completion handler to be invoked.") - - // When - gradientActivityIndicatorView.fadeOut(duration: 0.1) { isFinished in - XCTAssertFalse(isFinished) - expectation.fulfill() - } - - // Cancel animation. - gradientActivityIndicatorView.layer.removeAllAnimations() - - // Then - wait(for: [expectation], timeout: 1) - - XCTAssertFalse(gradientActivityIndicatorView.isHidden, "As we've interrupted the animation, we expect the `isHidden` flag to still be `false`.") - } -} diff --git a/GradientLoadingBar/Tests/UnitTests/GradientActivityIndicatorView/GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift b/GradientLoadingBar/Tests/UnitTests/GradientActivityIndicatorView/GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift deleted file mode 100644 index 6f82f47..0000000 --- a/GradientLoadingBar/Tests/UnitTests/GradientActivityIndicatorView/GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// GradientActivityIndicatorViewModel+SizeUpdateTestCase.swift -// ExampleTests -// -// Created by Felix Mau on 18.08.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import XCTest - -@testable import GradientLoadingBar - -// swiftlint:disable:next type_name -final class GradientActivityIndicatorViewModel_SizeUpdateTestCase: XCTestCase { - - // MARK: - Test property `frame` - - func test_frame() { - // Given - let bounds = CGRect(x: 0, - y: 0, - width: .random(in: 0 ... .max), - height: .random(in: 0 ... .max)) - - // When - let sizeUpdate = GradientActivityIndicatorViewModel.SizeUpdate(bounds: bounds) - - // Then - let expectedFrame = CGRect(x: 0, - y: 0, - width: bounds.width * 3, - height: bounds.height) - - XCTAssertEqual(sizeUpdate.frame, expectedFrame) - } - - // MARK: - Test property `fromValue` - - func test_fromValue() { - // Given - let bounds = CGRect(x: 0, - y: 0, - width: .random(in: 0 ... .max), - height: .random(in: 0 ... .max)) - - // When - let sizeUpdate = GradientActivityIndicatorViewModel.SizeUpdate(bounds: bounds) - - // Then - let expectedFromValue = bounds.width * -2 - XCTAssertEqual(sizeUpdate.fromValue, expectedFromValue) - } -} diff --git a/GradientLoadingBar/Tests/UnitTests/GradientActivityIndicatorView/GradientActivityIndicatorViewModelTestCase.swift b/GradientLoadingBar/Tests/UnitTests/GradientActivityIndicatorView/GradientActivityIndicatorViewModelTestCase.swift deleted file mode 100644 index 2a1d3ac..0000000 --- a/GradientLoadingBar/Tests/UnitTests/GradientActivityIndicatorView/GradientActivityIndicatorViewModelTestCase.swift +++ /dev/null @@ -1,214 +0,0 @@ -// -// GradientActivityIndicatorViewModelTestCase.swift -// ExampleTests -// -// Created by Felix Mau on 26.08.19. -// Copyright © 2019 Felix Mau. All rights reserved. -// - -import XCTest - -@testable import GradientLoadingBar - -// swiftlint:disable:next type_name -final class GradientActivityIndicatorViewModelTestCase: XCTestCase { - - // MARK: - Private properties - - private var viewModel: GradientActivityIndicatorViewModel! - - // MARK: - Public methods - - override func setUp() { - super.setUp() - - viewModel = GradientActivityIndicatorViewModel() - } - - override func tearDown() { - viewModel = nil - - super.tearDown() - } - - // MARK: - Test property `isHidden` - - func test_settingIsHidden_toTrue_shouldUpdateIsAnimatingSubject_withFalse() throws { - // Given - var receivedIsAnimating: Bool? - let cancellable = viewModel.isAnimating.sink { isAnimating in - receivedIsAnimating = isAnimating - } - - // When - withExtendedLifetime(cancellable) { - viewModel.isHidden = true - } - - // Then - XCTAssertFalse( - try XCTUnwrap(receivedIsAnimating, "Expected to have received a value from the subscription closure at this point.") - ) - } - - func test_settingIsHidden_toFalse_shouldUpdateIsAnimatingSubject_withTrue() throws { - // Given - var receivedIsAnimating: Bool? - let cancellable = viewModel.isAnimating.sink { isAnimating in - receivedIsAnimating = isAnimating - } - - // When - withExtendedLifetime(cancellable) { - viewModel.isHidden = false - } - - // Then - XCTAssertTrue( - try XCTUnwrap(receivedIsAnimating, "Expected to have received a value from the subscription closure at this point.") - ) - } - - // MARK: - Test property `bounds` - - func test_settingBounds_shouldUpdateGradientLayerSizeUpdate() { - // Given - var receivedSizeUpdate: GradientActivityIndicatorViewModel.SizeUpdate? - let cancellable = viewModel.gradientLayerSizeUpdate.sink { sizeUpdate in - receivedSizeUpdate = sizeUpdate - } - - let size = CGSize(width: .random(in: 1 ... 100), height: .random(in: 1 ... 100)) - let bounds = CGRect(origin: .zero, size: size) - - // When - withExtendedLifetime(cancellable) { - viewModel.bounds = bounds - } - - // Then - let expectedSizeUpdate = GradientActivityIndicatorViewModel.SizeUpdate(bounds: bounds) - XCTAssertEqual(receivedSizeUpdate, expectedSizeUpdate) - } - - func test_settingBounds_shouldRestartAnimation() { - // Given - var receivedIsAnimating = [Bool]() - let cancellable = viewModel.isAnimating.sink { isAnimating in - receivedIsAnimating.append(isAnimating) - } - receivedIsAnimating.removeAll() - - let size = CGSize(width: .random(in: 1 ... 100), height: .random(in: 1 ... 100)) - let bounds = CGRect(origin: .zero, size: size) - - // When - withExtendedLifetime(cancellable) { - viewModel.bounds = bounds - } - - // Then - let expectedIsAnimating = [false, true] - XCTAssertEqual(receivedIsAnimating, expectedIsAnimating) - } - - func test_settingBounds_shouldNotRestartAnimation_dueToViewIsHidden() { - // Given - var receivedIsAnimating = [Bool]() - let cancellable = viewModel.isAnimating.sink { isAnimating in - receivedIsAnimating.append(isAnimating) - } - - let size = CGSize(width: .random(in: 1 ... 100), height: .random(in: 1 ... 100)) - let bounds = CGRect(origin: .zero, size: size) - - viewModel.isHidden = true - receivedIsAnimating.removeAll() - - // When - withExtendedLifetime(cancellable) { - viewModel.bounds = bounds - } - - // Then - XCTAssertTrue(receivedIsAnimating.isEmpty) - } - - // MARK: - Test property `gradientColors` - - func test_settingGradientColors_shouldUpdateGradientLayerColors() { - // Given - var receivedGradientLayerColors: [CGColor]? - let cancellable = viewModel.gradientLayerColors.sink { gradientLayerColors in - receivedGradientLayerColors = gradientLayerColors - } - - let gradientColors: [UIColor] = [.red, .yellow, .green] - - // When - withExtendedLifetime(cancellable) { - viewModel.gradientColors = gradientColors - } - - // Then - let expectedGradientLayerColors = [UIColor.red, .yellow, .green, .yellow, .red, .yellow, .green].map(\.cgColor) - XCTAssertEqual(receivedGradientLayerColors, expectedGradientLayerColors) - } - - // MARK: - Test property `progressAnimationDuration` - - func test_settingProgressAnimationDuration_shouldUpdateGradientLayerAnimationDuration() { - // Given - var receivedGradientLayerAnimationDuration: TimeInterval? - let cancellable = viewModel.gradientLayerAnimationDuration.sink { gradientLayerAnimationDuration in - receivedGradientLayerAnimationDuration = gradientLayerAnimationDuration - } - - let progressAnimationDuration: TimeInterval = .random(in: 0 ... 100) - - // When - withExtendedLifetime(cancellable) { - viewModel.progressAnimationDuration = progressAnimationDuration - } - - // Then - XCTAssertEqual(receivedGradientLayerAnimationDuration, progressAnimationDuration) - } - - func test_settingProgressAnimationDuration_shouldRestartAnimation() { - // Given - var receivedIsAnimating = [Bool]() - let cancellable = viewModel.isAnimating.sink { isAnimating in - receivedIsAnimating.append(isAnimating) - } - receivedIsAnimating.removeAll() - - // When - withExtendedLifetime(cancellable) { - viewModel.progressAnimationDuration = .random(in: 0 ... 100) - } - - // Then - let expectedIsAnimating = [false, true] - XCTAssertEqual(receivedIsAnimating, expectedIsAnimating) - } - - func test_settingProgressAnimationDuration_shouldNotRestartAnimation_dueToViewIsHidden() { - // Given - var receivedIsAnimating = [Bool]() - let cancellable = viewModel.isAnimating.sink { isAnimating in - receivedIsAnimating.append(isAnimating) - } - - viewModel.isHidden = true - receivedIsAnimating.removeAll() - - // When - withExtendedLifetime(cancellable) { - viewModel.progressAnimationDuration = .random(in: 0 ... 100) - } - - // Then - XCTAssertTrue(receivedIsAnimating.isEmpty) - } -} diff --git a/GradientLoadingBar/Tests/UnitTests/GradientLoadingBar/GradientLoadingBarViewModelTestCase.swift b/GradientLoadingBar/Tests/UnitTests/GradientLoadingBar/GradientLoadingBarViewModelTestCase.swift deleted file mode 100644 index c07de9f..0000000 --- a/GradientLoadingBar/Tests/UnitTests/GradientLoadingBar/GradientLoadingBarViewModelTestCase.swift +++ /dev/null @@ -1,127 +0,0 @@ -// -// GradientLoadingBarViewModelTestCase.swift -// ExampleTests -// -// Created by Felix Mau on 26.12.17. -// Copyright © 2017 Felix Mau. All rights reserved. -// - -import Combine -import XCTest - -@testable import GradientLoadingBar - -final class GradientLoadingBarViewModelTestCase: XCTestCase { - - // MARK: - Private properties - - private var sharedApplicationMock: SharedApplicationMock! - private var notificationCenter: NotificationCenter! - - // MARK: - Public methods - - override func setUp() { - super.setUp() - - sharedApplicationMock = SharedApplicationMock() - notificationCenter = NotificationCenter() - } - - override func tearDown() { - notificationCenter = nil - sharedApplicationMock = nil - - super.tearDown() - } - - // MARK: - Test observable `superview` - - func test_init_shouldSetupSuperviewObservable_withNil() throws { - // When - let viewModel = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock, - notificationCenter: notificationCenter) - - var receivedValues = [UIView?]() - let cancellable = viewModel.superview.sink { - receivedValues.append($0) - } - - // Then - withExtendedLifetime(cancellable) { - XCTAssertEqual(receivedValues, [nil]) - } - } - - func test_init_shouldSetupSuperviewObservable_withKeyWindow() throws { - // Given - let keyWindow = UIWindow() - sharedApplicationMock.keyWindowInConnectedScenes = keyWindow - - // When - let viewModel = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock, - notificationCenter: notificationCenter) - - // Then - var receivedValues = [UIView?]() - let cancellable = viewModel.superview.sink { - receivedValues.append($0) - } - - // Then - withExtendedLifetime(cancellable) { - XCTAssertEqual(receivedValues, [keyWindow]) - } - } - - func test_init_shouldSetupSuperviewObservable_afterUIWindowDidBecomeKeyNotification() { - // Given - let viewModel = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock, - notificationCenter: notificationCenter) - - var receivedKeyWindows = [UIView?]() - let cancellable = viewModel.superview.sink { keyWindow in - receivedKeyWindows.append(keyWindow) - } - - let keyWindow = UIWindow() - sharedApplicationMock.keyWindowInConnectedScenes = keyWindow - - // When - withExtendedLifetime(cancellable) { - notificationCenter.post(name: UIWindow.didBecomeKeyNotification, object: nil) - } - - // Then - let expectedKeyWindows = [nil, keyWindow] - XCTAssertEqual(receivedKeyWindows, expectedKeyWindows) - } - - func test_deinit_shouldResetSuperviewObservable_withNil() { - // Given - let keyWindow = UIWindow() - sharedApplicationMock.keyWindowInConnectedScenes = keyWindow - - var viewModel: GradientLoadingBarViewModel? = GradientLoadingBarViewModel(sharedApplication: sharedApplicationMock, - notificationCenter: notificationCenter) - - var receivedKeyWindows = [UIView?]() - let cancellable = viewModel?.superview.sink { keyWindow in - receivedKeyWindows.append(keyWindow) - } - - // When - withExtendedLifetime(cancellable) { - viewModel = nil - } - - // Then - let expectedKeyWindows = [keyWindow, nil] - XCTAssertEqual(receivedKeyWindows, expectedKeyWindows) - } -} - -// MARK: - Mocks - -private final class SharedApplicationMock: UIApplicationProtocol { - var keyWindowInConnectedScenes: UIWindow? -} diff --git a/GradientLoadingBar/Tests/UnitTests/GradientLoadingBarView/GradientLoadingBarView+ViewModelTestCase.swift b/GradientLoadingBar/Tests/UnitTests/GradientLoadingBarView/GradientLoadingBarView+ViewModelTestCase.swift deleted file mode 100644 index ee9012c..0000000 --- a/GradientLoadingBar/Tests/UnitTests/GradientLoadingBarView/GradientLoadingBarView+ViewModelTestCase.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// GradientLoadingBarView+ViewModelTestCase.swift -// ExampleTests -// -// Created by Felix Mau on 22.03.22. -// Copyright © 2022 Felix Mau. All rights reserved. -// - -import SwiftUI -import XCTest - -@testable import GradientLoadingBar - -@available(iOS 15.0, *) -final class GradientLoadingBarView_ViewModelTestCase: XCTestCase { - // swiftlint:disable:previous type_name - - // MARK: - Test property `gradientColors` - - func test_initialGradientColors_shouldIncludeReversedGradientColors() { - // Given - let gradientColors: [Color] = [.red, .yellow, .green] - - // When - let viewModel = GradientLoadingBarView.ViewModel(gradientColors: gradientColors, progressDuration: 1) - - // Then - let expectedGradientColors: [Color] = [.red, .yellow, .green, .yellow, .red, .yellow, .green] - XCTAssertEqual(viewModel.gradientColors, expectedGradientColors) - } - - // MARK: - Test property `horizontalOffset` - - func test_initialHorizontalOffset_shouldBeZero() { - // When - let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1) - - // Then - XCTAssertEqual(viewModel.horizontalOffset, 0) - } - - func test_settingSize_shouldUpdateHorizontalOffset() { - // Given - let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1) - let size = CGSize(width: .random(in: 1 ... 100_000), height: .random(in: 1 ... 100_000)) - - var receivedValues = [CGFloat]() - let subscription = viewModel.$horizontalOffset.sink { - receivedValues.append($0) - } - - withExtendedLifetime(subscription) { - // When - viewModel.size = size - } - - // Then - let expectedValues = [0, -size.width, size.width] - XCTAssertEqual(receivedValues, expectedValues) - } - - // MARK: - Test property `gradientWidth` - - func test_initialGradientWidth_shouldBeZero() { - // When - let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1) - - // Then - XCTAssertEqual(viewModel.gradientWidth, 0) - } - - func test_settingSize_shouldUpdateGradientWidth() { - // Given - let viewModel = GradientLoadingBarView.ViewModel(gradientColors: [], progressDuration: 1) - let size = CGSize(width: .random(in: 1 ... 100_000), height: .random(in: 1 ... 100_000)) - - // When - viewModel.size = size - - // Then - XCTAssertEqual(viewModel.gradientWidth, size.width * 3) - } -} diff --git a/GradientLoadingBar/Tests/UnitTests/NotchGradientLoadingBar/NotchGradientLoadingBarViewModelTestCase.swift b/GradientLoadingBar/Tests/UnitTests/NotchGradientLoadingBar/NotchGradientLoadingBarViewModelTestCase.swift deleted file mode 100644 index e171c52..0000000 --- a/GradientLoadingBar/Tests/UnitTests/NotchGradientLoadingBar/NotchGradientLoadingBarViewModelTestCase.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// NotchGradientLoadingBarViewModelTestCase.swift -// ExampleTests -// -// Created by Felix Mau on 26.12.17. -// Copyright © 2017 Felix Mau. All rights reserved. -// - -import XCTest - -@testable import GradientLoadingBar - -final class NotchGradientLoadingBarViewModelTestCase: XCTestCase { - - func test_initializer_shouldSetCorrectNotchDevice() { - // Given - let identifiersToNotchDeviceMap: [String: NotchGradientLoadingBarViewModel.NotchDevice] = [ - "iPhone10,3": .iPhoneX, - "iPhone10,6": .iPhoneX, - "iPhone11,2": .iPhoneXS, - "iPhone11,4": .iPhoneXSMax, - "iPhone11,6": .iPhoneXSMax, - "iPhone11,8": .iPhoneXR, - "iPhone12,1": .iPhone11, - "iPhone12,3": .iPhone11Pro, - "iPhone12,5": .iPhone11ProMax, - "iPhone13,1": .iPhone12Mini, - "iPhone13,2": .iPhone12, - "iPhone13,3": .iPhone12Pro, - "iPhone13,4": .iPhone12ProMax, - "iPhone14,4": .iPhone13Mini, - "iPhone14,5": .iPhone13, - "iPhone14,2": .iPhone13Pro, - "iPhone14,3": .iPhone13ProMax, - "iPhone14,7": .iPhone14, - "iPhone14,8": .iPhone14Plus, - ] - - identifiersToNotchDeviceMap.forEach { deviceIdentifier, notchDevice in - // When - let viewModel = NotchGradientLoadingBarViewModel(deviceIdentifier: deviceIdentifier) - - // Then - XCTAssertEqual(viewModel.notchDevice, notchDevice) - } - } - - func test_initializer_shouldSetNotchDevice_toNil() { - // Given - let deviceIdentifier = "Foo-Bar-đŸ€Ą" - - // When - let viewModel = NotchGradientLoadingBarViewModel(deviceIdentifier: deviceIdentifier) - - // Then - XCTAssertNil(viewModel.notchDevice) - } -} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b58d9c2 --- /dev/null +++ b/Makefile @@ -0,0 +1,95 @@ +## +## ================ +## Gradient Loading Bar Makefile +## ================ +## + + +######### Tools ######### + +SWIFTFORMAT := swiftformat +SWIFTLINT := swiftlint +XCODEBUILD := xcodebuild + + +######### SwiftFormat ######### + +## $ make verify-swiftformat-installed +## Verifies that SwiftFormat is installed on the system. +## +.PHONY: verify-swiftformat-installed +verify-swiftformat-installed: + @command -v $(SWIFTFORMAT) >/dev/null 2>&1 || { \ + echo "warning: SwiftFormat not installed!"; \ + exit 1; \ + } + +## $ make format +## Uses SwiftFormat to automatically reformat the codebase according +## to our guidelines. +## +.PHONY: format +format: verify-swiftformat-installed + @$(SWIFTFORMAT) ./ + +## $ make format-check +## Runs SwiftFormat in lint mode (no changes). Intended for CI. +## +.PHONY: format-check +format-check: verify-swiftformat-installed + @$(SWIFTFORMAT) --lint ./ + + +######### SwiftLint ######### + +## $ make verify-swiftlint-installed +## Verifies that SwiftLint is installed on the system. +## +.PHONY: verify-swiftlint-installed +verify-swiftlint-installed: + @command -v $(SWIFTLINT) >/dev/null 2>&1 || { \ + echo "warning: SwiftLint not installed!"; \ + exit 1; \ + } + +## $ make lint +## Runs SwiftLint on the whole project according to the linting configuration files. +## +.PHONY: lint +lint: verify-swiftlint-installed + @$(SWIFTLINT) --strict ./ + + +######### XcodeBuild ######### + +## $ make verify-xcodebuild-installed +## Verifies that xcodebuild is installed on the system. +## +.PHONY: verify-xcodebuild-installed +verify-xcodebuild-installed: + @command -v $(XCODEBUILD) >/dev/null 2>&1 || { \ + echo "warning: xcodebuild not installed!"; \ + exit 1; \ + } + +## $ make test +## Runs the test suite using xcodebuild. +## +.PHONY: test +test: verify-xcodebuild-installed + @$(XCODEBUILD) \ + test \ + -scheme GradientLoadingBar \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -enableCodeCoverage YES + +## $ make build-example-application +## Builds the Example Application using xcodebuild. +## +.PHONY: build-example-application +build-example-application: verify-xcodebuild-installed + @$(XCODEBUILD) \ + build \ + -project Example/GradientLoadingBarExample.xcodeproj \ + -scheme GradientLoadingBarExample \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..23e6c33 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,42 @@ +{ + "originHash" : "cd46070e263d7e82daf5f75f4108c00524efe8575ebbee341d96606f2eb9210b", + "pins" : [ + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "93a8aa4937030b606de42f44b17870249f49af0b", + "version" : "1.3.4" + } + }, + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "a8b7c5e0ed33d8ab8887d1654d9b59f2cbad529b", + "version" : "1.18.7" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "4799286537280063c85a32f09884cfbca301b1a1", + "version" : "602.0.0" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "34e463e98ab8541c604af706c99bed7160f5ec70", + "version" : "1.8.1" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift index d015633..fcdec2b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,17 +1,45 @@ -// swift-tools-version:5.5 +// swift-tools-version: 6.2 +// The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription -let package = Package(name: "GradientLoadingBar", - platforms: [.iOS(.v13)], - products: [ - .library(name: "GradientLoadingBar", - targets: ["GradientLoadingBar"]), - ], - targets: [ - .target(name: "GradientLoadingBar", - path: "GradientLoadingBar/Sources"), - .testTarget(name: "GradientLoadingBarTests", - dependencies: ["GradientLoadingBar"], - path: "GradientLoadingBar/Tests/"), - ]) +let package = Package( + name: "GradientLoadingBar", + platforms: [.iOS(.v26)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "GradientLoadingBar", + targets: ["GradientLoadingBar"], + ), + ], + dependencies: [ + .package( + url: "https://github.com/pointfreeco/swift-snapshot-testing", + from: "1.12.0", + ), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "GradientLoadingBar", + ), + .testTarget( + name: "GradientLoadingBarTests", + dependencies: [ + "GradientLoadingBar", + .product( + name: "SnapshotTesting", + package: "swift-snapshot-testing", + ), + ], + exclude: [ + "SnapshotTests/README.md", + ], + resources: [ + .copy("SnapshotTests/__Snapshots__"), + ], + ), + ], +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd0f329 --- /dev/null +++ b/README.md @@ -0,0 +1,346 @@ +# Gradient Loading Bar + +![Swift6.2](https://img.shields.io/badge/Swift-6.2-green.svg?style=flat) ![CI Status](https://img.shields.io/github/actions/workflow/status/fxm90/GradientLoadingBar/continuous-integration.yml) ![Code Coverage](https://img.shields.io/codecov/c/github/fxm90/GradientLoadingBar.svg?style=flat) ![Version](https://img.shields.io/github/v/release/fxm90/GradientLoadingBar) ![License](https://img.shields.io/github/license/fxm90/GradientLoadingBar?color=333333) ![Platform](https://img.shields.io/badge/platform-iOS-8a8a8a) + +A customizable animated gradient loading bar, with full support for **SwiftUI** and **UIKit**.\ +Inspired by [iOS 7 Progress Bar on CodePen](https://codepen.io/marcobiedermann/pen/LExXWW). + +### Screenshot + +![Example][example] + +To run the example project, clone this repository and open the project file from the **Example** directory. + +## Requirements + +- Swift **6.2** +- Xcode **26** +- iOS **26.0+** + +### Compatibility Notes + +- **iOS < 26.0 / CocoaPods / Carthage support** + Use version **`3.x.x`** +- **iOS < 13.0 support** + Use version **`2.x.x`** + +## Installation + +### Swift Package Manager + +**Gradient Loading Bar** is distributed via **Swift Package Manager (SPM)**. Add it to your Xcode project as a package dependency or adapt your `Package.swift` file. + +#### Option 1: Xcode + +1. Open your project in **Xcode** +2. Go to **File → Add Packages
** +3. Enter the package URL: `https://github.com/fxm90/GradientLoadingBar` +4. Choose the version rule (e.g. _Up to Next Major_ starting at **4.0.0**) +5. Add the package to your target + +#### Option 2: `Package.swift` + +If you manage dependencies manually, add this repository to the `dependencies` section of your `Package.swift`: + +```swift +dependencies: [ + .package( + url: "https://github.com/fxm90/GradientLoadingBar", + from: "4.0.0" + ) +] +``` + +Then reference the product in your target configuration: + +```swift +.product( + name: "GradientLoadingBar", + package: "GradientLoadingBar" +) +``` + +Once the package is added, import the framework where needed: + +```swift +import GradientLoadingBar +``` + +## Overview + +This framework provides four types: + +- **[`GradientLoadingBar`](#gradientloadingbar)**\ + A controller class, managing the visibility of the `GradientActivityIndicatorView` attached to the current key window. + +- **[`NotchGradientLoadingBar`](#notchgradientloadingbar)**\ + A subclass of `GradientLoadingBar`, wrapping the `GradientActivityIndicatorView` around the iPhone notch. + +- **[`GradientActivityIndicatorView`](#gradientactivityindicatorview)**\ + A `UIView` containing the gradient with the animation. + +- **[`GradientLoadingBarView`](#gradientloadingbarview)**\ + A SwiftUI `View` containing the gradient with the animation. + +## GradientLoadingBar + +`GradientLoadingBar` is a controller-style object that manages a gradient loading bar attached to the app’s key window. + +### Usage + +For most use cases, prefer the shared instance provided by `GradientLoadingBar.shared`. + +```swift +final class UserProfileViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + GradientLoadingBar.shared.fadeIn() + + userProfileService.fetchUserProfile { _ in + // ... + // Be sure to call this from the main actor! + GradientLoadingBar.shared.fadeOut() + } + } +} +``` + +This shared instance can be overridden to customize the loading bar globally. + +### Configuration + +```swift +let gradientLoadingBar = GradientLoadingBar( + height: 5.0, + isRelativeToSafeArea: false, +) + +gradientLoadingBar.gradientColors = [.systemIndigo, .systemPurple, .systemPink] +gradientLoadingBar.progressAnimationDuration = 4.5 +``` + +#### Parameters + +- **`height: CGFloat`**\ + Sets the bar height (default: `3.0`) + +- **`isRelativeToSafeArea: Bool`**\ + Determines whether the bar is positioned relative to the safe area (default: `true`) + +| Relative To Safe Area | Ignoring Safe Area | +| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| [![Relative to Safe Area][relative-to-safe-area-thumbnail]][relative-to-safe-area] | [![Ignoring Safe Area][ignoring-safe-area-thumbnail]][ignoring-safe-area] | + +The gradient animation is easier to see in a video of the example application: + +
+ 🎬 Relative To Safe Area + + https://github.com/user-attachments/assets/c8a3a117-c164-49f5-9511-aa29b92b5344 +
+ +
+ 🎬 Ignoring Safe Area + + https://github.com/user-attachments/assets/4abad13d-e019-43b3-b3b3-c874ac8f9224 +
+ +**Note:** For a third option — wrapping around the iPhone notch — see `NotchGradientLoadingBar`. + +#### Properties + +- **`gradientColors: [UIColor]`**\ + Defines the gradient colors. + +- **`progressAnimationDuration: TimeInterval`**\ + Controls how fast the gradient animates from left to right. + +#### Methods + +- **`fadeIn(duration: TimeInterval = 0.33, completion: ((Bool) -> Void)? = nil)`**\ + Fades the loading bar in. + +- **`fadeOut(duration: TimeInterval = 0.66, completion: ((Bool) -> Void)? = nil)`**\ + Fades the loading bar out. + +## NotchGradientLoadingBar + +`NotchGradientLoadingBar` is a subclass of `GradientLoadingBar` that wraps the bar around the iPhone notch.\ +On devices without a notch, it behaves identically to `GradientLoadingBar`. + +| iPhone 12 Pro | iPhone 13 Pro Max | +| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| [![iPhone 12 Pro][notch-iphone-12-pro-thumbnail]][notch-iphone-12-pro] | [![iPhone 13 Pro Max][notch-iphone-13-pro-max-thumbnail]][notch-iphone-13-pro-max] | + +The gradient animation is easier to see in a video of the example application: + +
+ 🎬 iPhone 12 Pro + + https://github.com/user-attachments/assets/998fe36c-40c5-4c70-9ce8-e7a7f3f126d8 +
+ +
+ 🎬 iPhone 13 Pro Max + + https://github.com/user-attachments/assets/2ff6d717-07f1-4d70-887e-2727d55f2515 +
+ +## GradientActivityIndicatorView + +If you prefer direct view composition, `GradientActivityIndicatorView` is a `UIView` subclass that can be added to other views — such as `UINavigationBar`, `UIButton` or a custom container. + +### Usage + +Store an instance as a property, set-up constraints and control the visibility using `fadeIn()` and `fadeOut()`. + +```swift +final class UserProfileViewController: UIViewController { + + private let gradientActivityIndicatorView = GradientActivityIndicatorView() + + // ... + + override func viewDidLoad() { + super.viewDidLoad() + + gradientActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(gradientActivityIndicatorView) + + NSLayoutConstraint.activate([ + gradientActivityIndicatorView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + gradientActivityIndicatorView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + gradientActivityIndicatorView.topAnchor.constraint(equalTo: view.topAnchor), + gradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: 3), + ]) + + gradientActivityIndicatorView.fadeIn() + + userProfileService.fetchUserProfile { [weak self] _ in + // ... + // Be sure to call this from the main actor! + self?.gradientActivityIndicatorView.fadeOut() + } + } +} +``` + +### Configuration + +```swift +let gradientActivityIndicatorView = GradientActivityIndicatorView() +gradientActivityIndicatorView.gradientColors = [.systemIndigo, .systemPurple, .systemPink] +gradientActivityIndicatorView.progressAnimationDuration = 4.5 +``` + +#### Properties + +- **`isHidden: Bool`**\ + Toggles the view visibility and starts / stops the progress animation accordingly. + +- **`gradientColors: [UIColor]`**\ + Defines the gradient colors. + +- **`progressAnimationDuration: TimeInterval`**\ + Controls how fast the gradient animates from left to right. + +#### Methods + +- **`fadeIn(duration: TimeInterval = 0.33, completion: ((Bool) -> Void)? = nil)`**\ + Fades the view in. + +- **`fadeOut(duration: TimeInterval = 0.66, completion: ((Bool) -> Void)? = nil)`**\ + Fades the view out. + +- **`animate(isHidden: Bool, duration: TimeInterval, completion: ((Bool) -> Void)? = nil)`**\ + Fades the view in- or out, based on the provided hidden flag. + +## GradientLoadingBarView + +`GradientLoadingBarView` is the SwiftUI equivalent of `GradientActivityIndicatorView`. + +### Usage + +```swift +struct ContentView: some View { + + var body: some View { + GradientLoadingBarView() + .frame(maxWidth: .infinity, maxHeight: 3) + .cornerRadius(1.5) + } +} +``` + +### Configuration + +```swift +GradientLoadingBarView( + gradientColors: [.indigo, .purple, .pink], + progressDuration: 4.5, +) +``` + +#### Parameters + +- **`gradientColors: [Color]`**\ + Defines the gradient colors. + +- **`progressDuration: TimeInterval`**\ + Sets the animation speed. + +### Visibility & Animation + +Control the visibility using standard SwiftUI view modifiers such as: + +- [`opacity(_:)`]() +- [`hidden()`]() + +Example with fade animation. + +```swift +struct ContentView: some View { + + @State + private var isVisible = false + + var body: some View { + VStack { + GradientLoadingBarView() + .frame(maxWidth: .infinity, maxHeight: 3) + .cornerRadius(1.5) + .opacity(isVisible ? 1 : 0) + .animation(.easeInOut, value: isVisible) + + Button("Toggle visibility") { + isVisible.toggle() + } + } + } +} +``` + +## Author + +Felix Mau +me(@)felix.hamburg + +## License + +Gradient Loading Bar is released under the **MIT License**. See the `LICENSE` file for details. + +[example]: Assets/screen.gif +[ignoring-safe-area]: Assets/ignoring-safe-area.png +[ignoring-safe-area-thumbnail]: Assets/ignoring-safe-area-thumbnail.png +[ignoring-safe-area-animation]: Assets/ignoring-safe-area.mov +[relative-to-safe-area]: Assets/relative-to-safe-area.png +[relative-to-safe-area-thumbnail]: Assets/relative-to-safe-area-thumbnail.png +[relative-to-safe-area-animation]: Assets/relative-to-safe-area.mov +[notch-iphone-12-pro]: Assets/notch-iphone-12-pro.png +[notch-iphone-12-pro-thumbnail]: Assets/notch-iphone-12-pro-thumbnail.png +[notch-iphone-12-pro-animation]: Assets/notch-iphone-12-pro.mov +[notch-iphone-13-pro-max]: Assets/notch-iphone-13-pro-max.png +[notch-iphone-13-pro-max-thumbnail]: Assets/notch-iphone-13-pro-max-thumbnail.png +[notch-iphone-13-pro-max-animation]: Assets/notch-iphone-13-pro-max.mov diff --git a/Sources/GradientLoadingBar/Config/CGFloat+GradientLoadingBar.swift b/Sources/GradientLoadingBar/Config/CGFloat+GradientLoadingBar.swift new file mode 100644 index 0000000..cde29b8 --- /dev/null +++ b/Sources/GradientLoadingBar/Config/CGFloat+GradientLoadingBar.swift @@ -0,0 +1,18 @@ +// +// CGFloat+GradientLoadingBar.swift +// GradientLoadingBar +// +// Created by Felix Mau on 26.08.19. +// Copyright © 2019 Felix Mau. All rights reserved. +// + +import CoreGraphics + +public extension CGFloat { + + /// Numeric configuration values for `GradientLoadingBar`. + enum GradientLoadingBar { + /// The default height of the gradient loading bar, measured in points. + public static let height: CGFloat = 3 + } +} diff --git a/Sources/GradientLoadingBar/Config/TimeInterval+GradientLoadingBar.swift b/Sources/GradientLoadingBar/Config/TimeInterval+GradientLoadingBar.swift new file mode 100644 index 0000000..912edbd --- /dev/null +++ b/Sources/GradientLoadingBar/Config/TimeInterval+GradientLoadingBar.swift @@ -0,0 +1,24 @@ +// +// TimeInterval+GradientLoadingBar.swift +// GradientLoadingBar +// +// Created by Felix Mau on 26.08.19. +// Copyright © 2019 Felix Mau. All rights reserved. +// + +import Foundation + +public extension TimeInterval { + + /// Time related configuration values for `GradientLoadingBar`. + enum GradientLoadingBar { + /// The default duration for fading-in the loading bar, measured in seconds. + public static let fadeInDuration = 0.33 + + /// The default duration for fading-out the loading bar, measured in seconds. + public static let fadeOutDuration = 0.66 + + /// The default duration for the progress animation, measured in seconds. + public static let progressDuration = 3.33 + } +} diff --git a/Sources/GradientLoadingBar/Config/UIColor+GradientLoadingBar.swift b/Sources/GradientLoadingBar/Config/UIColor+GradientLoadingBar.swift new file mode 100644 index 0000000..9ea51dc --- /dev/null +++ b/Sources/GradientLoadingBar/Config/UIColor+GradientLoadingBar.swift @@ -0,0 +1,36 @@ +// +// UIColor+GradientLoadingBar.swift +// GradientLoadingBar +// +// Created by Felix Mau on 26.08.19. +// Copyright © 2019 Felix Mau. All rights reserved. +// + +import SwiftUI +import UIKit + +public extension UIColor { + + /// Color related configuration values for `GradientLoadingBar`. + enum GradientLoadingBar { + /// The default color palette for the gradient colors. + /// + /// Source: + public static let gradientColors = [ + #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), + ] + } +} + +public extension Color { + + /// Color related configuration values for `GradientLoadingBar`. + /// + /// - Note: Added in `UIColor` extension file, cause these values are derived from + /// to the UIKit configuration values to ensure consistency across both frameworks. + enum GradientLoadingBar { + /// The default color palette for the gradient colors. + public static let gradientColors = + UIColor.GradientLoadingBar.gradientColors.map(Color.init) + } +} diff --git a/Sources/GradientLoadingBar/Features/GradientActivityIndicatorView/GradientActivityIndicatorView+ViewModel.swift b/Sources/GradientLoadingBar/Features/GradientActivityIndicatorView/GradientActivityIndicatorView+ViewModel.swift new file mode 100644 index 0000000..aa5f835 --- /dev/null +++ b/Sources/GradientLoadingBar/Features/GradientActivityIndicatorView/GradientActivityIndicatorView+ViewModel.swift @@ -0,0 +1,100 @@ +// +// GradientActivityIndicatorView+ViewModel.swift +// GradientLoadingBar +// +// Created by Felix Mau on 26.08.19. +// Copyright © 2019 Felix Mau. All rights reserved. +// + +import Observation +import UIKit + +extension GradientActivityIndicatorView { + + /// This view model contains all logic related to the `GradientActivityIndicatorView` + /// and the corresponding progress animation. + @Observable + final class ViewModel { + + // MARK: - Internal Properties + + /// Boolean flag whether the view is currently hidden. + var isHidden = false + + /// The state of the progress animation. + var isAnimating: Bool { + !isHidden + } + + /// The bounds of the view. + var bounds: CGRect = .zero + + /// The frame of the gradient layer. + var gradientLayerFrame: CGRect { + var frame = bounds + // The width has to be three times the width of the parent bounds in order to apply the normal, reversed and + // again normal gradient colors to simulate the infinite animation. + frame.size.width *= 3 + + return frame + } + + /// The 'fromValue' for the progress animation. + var animationFromValue: CGFloat { + bounds.width * -2 + } + + /// The 'toValue' for the progress animation. + var animationToValue: CGFloat { + 0 + } + + /// The color array used for the gradient (of type `[UIColor]`). + var gradientColors = UIColor.GradientLoadingBar.gradientColors + + /// The color array for the gradient layer (of type `[CGColor]`). + var gradientLayerColors: [CGColor] { + gradientColors + .infiniteGradientColors() + .map(\.cgColor) + } + + /// The duration for the progress animation. + /// + /// - Note: We explicitly have to pass this value through the view-model, in order to restart the animation when + /// this value changes while the loading bar is visible. + var progressAnimationDuration: TimeInterval = .GradientLoadingBar.progressDuration + } +} + +// MARK: - Helper + +private extension [UIColor] { + /// Returns a color sequence suitable for a seamless, looping gradient animation. + /// + /// The resulting array mirrors the colors back and forth so the gradient can animate + /// continuously without visible seams or duplicated colors at the turning points. + /// + /// To avoid duplicate colors at the inner edges, the reversed sequence omits + /// the first and last elements before being appended. + /// + /// Example: + /// ``` + /// [.red, .yellow, .green] + /// → [.red, .yellow, .green, .yellow, .red, .yellow, .green] + /// + /// [.red, .yellow, .green, .blue] + /// → [.red, .yellow, .green, .blue, .green, .yellow, .red, .yellow, .green, .blue] + /// ``` + /// + /// - Returns: A color array that can be animated left-to-right and looped without a visible jump. + func infiniteGradientColors() -> [UIColor] { + guard count > 1 else { return self } + + let reversedColors = reversed() + .dropFirst() + .dropLast() + + return self + reversedColors + self + } +} diff --git a/Sources/GradientLoadingBar/Features/GradientActivityIndicatorView/GradientActivityIndicatorView.swift b/Sources/GradientLoadingBar/Features/GradientActivityIndicatorView/GradientActivityIndicatorView.swift new file mode 100644 index 0000000..4d120ec --- /dev/null +++ b/Sources/GradientLoadingBar/Features/GradientActivityIndicatorView/GradientActivityIndicatorView.swift @@ -0,0 +1,212 @@ +// +// GradientActivityIndicatorView.swift +// GradientLoadingBar +// +// Created by Felix Mau on 12.10.16. +// Copyright © 2016 Felix Mau. All rights reserved. +// + +import UIKit + +/// A view that displays an animated horizontal gradient, commonly used as a loading indicator. +/// +/// ## Overview +/// +/// `GradientActivityIndicatorView` renders a customizable color gradient that continuously +/// animates from left to right, creating an infinite scrolling effect. +/// +/// ## Usage +/// +/// ```swift +/// let gradientActivityIndicatorView = GradientActivityIndicatorView() +/// gradientActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false +/// view.addSubview(loadingBar) +/// +/// NSLayoutConstraint.activate([ +/// gradientActivityIndicatorView.leadingAnchor.constraint(equalTo: view.leadingAnchor), +/// gradientActivityIndicatorView.trailingAnchor.constraint(equalTo: view.trailingAnchor), +/// gradientActivityIndicatorView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), +/// gradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: 3) +/// ]) +/// +/// // Fade the view in. +/// gradientActivityIndicatorView.fadeIn() +/// +/// // Fade the view out when finished. +/// gradientActivityIndicatorView.fadeOut() +/// ``` +/// +/// ## Configuration +/// +/// You can customize the appearance and animation: +/// +/// ```swift +/// loadingBar.gradientColors = [.systemIndigo, .systemPurple, .systemPink] +/// loadingBar.progressAnimationDuration = 2.0 +/// ``` +/// +/// ## Animated Visibility Changes +/// +/// For smooth fade-in and fade-out transitions, use the convenience animation methods: +/// +/// ```swift +/// gradientActivityIndicatorView.fadeIn() +/// gradientActivityIndicatorView.fadeOut() +/// ``` +/// +/// ## Immediate Visibility Changes +/// +/// For instant show/hide without animation, use the `isHidden` property: +/// +/// ```swift +/// gradientActivityIndicatorView.isHidden = false +/// gradientActivityIndicatorView.isHidden = true +/// ``` +/// +/// ## Accessibility +/// +/// The view is automatically hidden from accessibility technologies, +/// as it serves as a visual indicator only. +/// +/// - Note: This view passes all touch events through to underlying views, making it safe +/// to overlay on interactive content without blocking user interaction. +/// +/// - SeeAlso: ``GradientLoadingBarView`` for the SwiftUI equivalent. +public class GradientActivityIndicatorView: UIView { + + // MARK: - Config + + private enum Config { + /// Animation-Key for the progress animation. + static let progressAnimationKey = "progressAnimation" + } + + // MARK: - Public Properties + + /// A Boolean value that determines whether the view is hidden. + /// + /// When set to `false`, the view becomes visible and starts the gradient animation. + /// When set to `true`, the animation stops and the view is hidden. + /// + /// For animated visibility changes, use ``fadeIn(duration:completion:)`` or + /// ``fadeOut(duration:completion:)`` instead of setting this property directly. + override public var isHidden: Bool { + didSet { + viewModel.isHidden = isHidden + } + } + + /// The colors used for the gradient. + public var gradientColors: [UIColor] { + get { viewModel.gradientColors } + set { viewModel.gradientColors = newValue } + } + + /// The duration for one complete animation cycle, measured in seconds. + public var progressAnimationDuration: TimeInterval { + get { viewModel.progressAnimationDuration } + set { viewModel.progressAnimationDuration = newValue } + } + + // MARK: - Private Properties + + /// The layer holding the gradient. + private let gradientLayer: CAGradientLayer = { + let layer = CAGradientLayer() + layer.anchorPoint = .zero + layer.startPoint = .zero + layer.endPoint = CGPoint(x: 1, y: 0) + + return layer + }() + + /// The progress animation. + private let progressAnimation: CABasicAnimation = { + let animation = CABasicAnimation(keyPath: "position.x") + animation.fromValue = 0 + animation.toValue = 0 + animation.duration = 0 + animation.isRemovedOnCompletion = false + animation.repeatCount = Float.infinity + + return animation + }() + + /// View model containing all logic related to this view. + private let viewModel = ViewModel() + + // MARK: - Instance Lifecycle + + override public init(frame: CGRect) { + super.init(frame: frame) + + setUpView() + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + setUpView() + } + + // MARK: - View Lifecycle + + override public func layoutSubviews() { + super.layoutSubviews() + + viewModel.bounds = bounds + } + + override public func updateProperties() { + super.updateProperties() + + // Unfortunately the only way to update a running `CABasicAnimation`, is to restart it. + // Mutating a running animation throws an exception! + gradientLayer.removeAnimation(forKey: Config.progressAnimationKey) + startProgressAnimationIfNeeded() + } + + override public func point(inside _: CGPoint, with _: UIEvent?) -> Bool { + // Passing all touches to the next view (if any), in the view stack. + false + } + + // MARK: - Private Methods + + private func setUpView() { + layer.addSublayer(gradientLayer) + layer.masksToBounds = true + + isAccessibilityElement = false + } + + private func startProgressAnimationIfNeeded() { + guard viewModel.isAnimating else { return } + + gradientLayer.frame = viewModel.gradientLayerFrame + gradientLayer.colors = viewModel.gradientLayerColors + progressAnimation.fromValue = viewModel.animationFromValue + progressAnimation.toValue = viewModel.animationToValue + progressAnimation.duration = viewModel.progressAnimationDuration + gradientLayer.add(progressAnimation, forKey: Config.progressAnimationKey) + } +} + +// MARK: - Preview + +#Preview { + let viewController = UIViewController() + + let gradientActivityIndicatorView = GradientActivityIndicatorView() + gradientActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false + viewController.view.addSubview(gradientActivityIndicatorView) + + NSLayoutConstraint.activate([ + gradientActivityIndicatorView.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor, constant: 24), + gradientActivityIndicatorView.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor, constant: -24), + gradientActivityIndicatorView.centerYAnchor.constraint(equalTo: viewController.view.centerYAnchor), + gradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: 4), + ]) + + return viewController +} diff --git a/Sources/GradientLoadingBar/Features/GradientActivityIndicatorView/Utilities/GradientActivityIndicatorView+AnimateIsHidden.swift b/Sources/GradientLoadingBar/Features/GradientActivityIndicatorView/Utilities/GradientActivityIndicatorView+AnimateIsHidden.swift new file mode 100644 index 0000000..6ee4618 --- /dev/null +++ b/Sources/GradientLoadingBar/Features/GradientActivityIndicatorView/Utilities/GradientActivityIndicatorView+AnimateIsHidden.swift @@ -0,0 +1,106 @@ +// +// GradientActivityIndicatorView+AnimateIsHidden.swift +// GradientLoadingBar +// +// Created by Felix Mau on 15.12.18. +// Copyright © 2018 Felix Mau. All rights reserved. +// + +import UIKit + +/// Convenience animation helpers for `GradientActivityIndicatorView` that +/// fade the view in and out while keeping the `isHidden` flag in sync. +/// +/// - Note: These methods are implemented as a public extension on `GradientActivityIndicatorView` +/// (rather than `UIView`) to avoid naming collisions with other frameworks. +/// +/// Source: +public extension GradientActivityIndicatorView { + + /// Animates the view from hidden or partially transparent to fully visible. + /// + /// - Parameters: + /// - duration: The animation duration, in seconds. + /// - completion: An optional closure invoked when the animation ends. + /// The Boolean parameter indicates whether the animation + /// completed successfully (`true`) or was interrupted (`false`). + func fadeIn( + duration: TimeInterval = .GradientLoadingBar.fadeInDuration, + completion: ((Bool) -> Void)? = nil, + ) { + if isHidden { + // Make sure our animation is visible. + isHidden = false + } + + UIView.animate( + withDuration: duration, + delay: 0, + options: [.beginFromCurrentState], + animations: { + self.alpha = 1 + }, + completion: completion, + ) + } + + /// Animates the view from visible to fully transparent. + /// + /// - Note: This method: + /// - Sets `isHidden` to `true` only if the animation completes successfully. + /// - Leaves `isHidden` as `false` if the animation is interrupted, + /// ensuring the view remains visible during overlapping animations. + /// + /// - Parameters: + /// - duration: The animation duration, in seconds. + /// - completion: An optional closure invoked when the animation ends. + /// The Boolean parameter indicates whether the animation + /// completed successfully (`true`) or was interrupted (`false`). + func fadeOut( + duration: TimeInterval = .GradientLoadingBar.fadeOutDuration, + completion: ((Bool) -> Void)? = nil, + ) { + UIView.animate( + withDuration: duration, + delay: 0, + options: [.beginFromCurrentState], + animations: { + self.alpha = 0 + }, + completion: { isFinished in + // Update `isHidden` flag accordingly: + // - set to `true` in case animation was completely finished. + // - set to `false` in case animation was interrupted, e.g. due to starting of another animation. + self.isHidden = isFinished + + completion?(isFinished) + }, + ) + } + + /// Animates the view’s visibility to match the given `isHidden` value. + /// + /// - Parameters: + /// - isHidden: A Boolean value indicating whether the view should be hidden. + /// - duration: The animation duration, in seconds. + /// - completion: An optional closure invoked when the animation ends. + /// The Boolean parameter indicates whether the animation + /// completed successfully (`true`) or was interrupted (`false`). + func animate( + isHidden: Bool, + duration: TimeInterval, + completion: ((Bool) -> Void)? = nil, + ) { + if isHidden { + fadeOut( + duration: duration, + completion: completion, + ) + } else { + fadeIn( + duration: duration, + completion: completion, + ) + } + } +} diff --git a/Sources/GradientLoadingBar/Features/GradientLoadingBarController/GradientLoadingBarController+ViewModel.swift b/Sources/GradientLoadingBar/Features/GradientLoadingBarController/GradientLoadingBarController+ViewModel.swift new file mode 100644 index 0000000..8db6f92 --- /dev/null +++ b/Sources/GradientLoadingBar/Features/GradientLoadingBarController/GradientLoadingBarController+ViewModel.swift @@ -0,0 +1,96 @@ +// +// GradientLoadingBarController+ViewModel.swift +// GradientLoadingBar +// +// Created by Felix Mau on 26.12.17. +// Copyright © 2017 Felix Mau. All rights reserved. +// + +import Combine +import UIKit + +/// A protocol that abstracts the view model for dependency injection. +/// This enables snapshot testing the controller without requiring a host application. +@MainActor +protocol GradientLoadingBarViewModel: AnyObject { + /// A publisher that emits the current superview (key window) for the gradient loading bar. + var superview: AnyPublisher { get } +} + +extension GradientLoadingBarController { + + /// This view model checks for the availability of the key-window, + /// and adds it as a superview to the gradient-view. + /// + /// - Note: While Observation works great with SwiftUI and UIKit views, + /// there’s a lot of work to be done for it to be usable from other places. + /// We therefore fallback to using Combine here. + @MainActor + final class ViewModel: GradientLoadingBarViewModel { + + // MARK: - Internal Properties + + /// Observable for the superview of the gradient-view. + var superview: AnyPublisher { + superviewSubject.eraseToAnyPublisher() + } + + // MARK: - Private Properties + + private let superviewSubject = CurrentValueSubject(nil) + private var subscriptions = Set() + + // MARK: - Instance Lifecycle + + init( + sharedApplication: UIApplicationProtocol = UIApplication.shared, + notificationCenter: NotificationCenter = .default, + ) { + if let keyWindow = sharedApplication.firstKeyWindow { + superviewSubject.value = keyWindow + } + + // The key window might be not available yet. This can happen, if the initializer is called from + // `UIApplicationDelegate.application(_:didFinishLaunchingWithOptions:)`. + // Also the key window can change. We therefore setup an observer be notified when a new window becomes the key window. + notificationCenter + .publisher(for: UIWindow.didBecomeKeyNotification) + .compactMap { _ in sharedApplication.firstKeyWindow } + .sink { [weak self] keyWindow in + self?.superviewSubject.value = keyWindow + } + .store(in: &subscriptions) + } + + @MainActor + deinit { + // Unsubscribe here, because the notification callback could theoretically fire during deinitialization. + subscriptions.removeAll() + + // Removes the gradient-view from its superview. + superviewSubject.value = nil + } + } +} + +// MARK: - Helper + +/// A protocol abstracting `UIApplication` for dependency injection. +/// This enables unit testing the view model. +@MainActor +protocol UIApplicationProtocol: AnyObject { + /// The first window in the connected scenes that is currently the key window. + var firstKeyWindow: UIWindow? { get } +} + +extension UIApplication: UIApplicationProtocol { + /// The first window in the connected scenes that is currently the key window. + /// + /// - SeeAlso: + var firstKeyWindow: UIWindow? { + connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap(\.windows) + .first { $0.isKeyWindow } + } +} diff --git a/Sources/GradientLoadingBar/Features/GradientLoadingBarController/GradientLoadingBarController.swift b/Sources/GradientLoadingBar/Features/GradientLoadingBarController/GradientLoadingBarController.swift new file mode 100644 index 0000000..3e460eb --- /dev/null +++ b/Sources/GradientLoadingBar/Features/GradientLoadingBarController/GradientLoadingBarController.swift @@ -0,0 +1,253 @@ +// +// GradientLoadingBarController.swift +// GradientLoadingBar +// +// Created by Felix Mau on 12.11.16. +// Copyright © 2016 Felix Mau. All rights reserved. +// + +import Combine +import UIKit + +/// Type-alias for the controller to match the package name. +public typealias GradientLoadingBar = GradientLoadingBarController + +/// A controller that manages an animated gradient loading bar positioned at the top of the screen. +/// +/// ## Overview +/// +/// `GradientLoadingBarController` provides a simple way to display a gradient loading indicator +/// similar to those seen in Safari or Instagram. The controller automatically adds its view to the +/// application's key window and positions it at the top of the screen. +/// +/// ## Basic Usage +/// +/// For most use cases, prefer the shared instance provided by `GradientLoadingBar.shared`. +/// +/// ```swift +/// // Fade the loading bar in. +/// GradientLoadingBar.shared.fadeIn() +/// +/// // Fade the loading bar out when finished. +/// GradientLoadingBar.shared.fadeOut() +/// ``` +/// +/// This shared instance can be overridden to customize the loading bar globally. +/// +/// ## Configuration +/// +/// You can create custom instances with different configurations: +/// +/// ```swift +/// let gradientLoadingBar = GradientLoadingBar( +/// height: 5, +/// isRelativeToSafeArea: false +/// ) +/// +/// gradientLoadingBar.gradientColors = [.systemIndigo, .systemPurple, .systemPink] +/// gradientLoadingBar.progressAnimationDuration = 4.5 +/// ``` +/// +/// ## Animated Visibility Changes +/// +/// For smooth fade-in and fade-out transitions, use the convenience animation methods: +/// +/// ```swift +/// gradientLoadingBar.fadeIn() +/// gradientLoadingBar.fadeOut() +/// ``` +/// +/// ## Immediate Visibility Changes +/// +/// For instant show/hide without animation, use the `isHidden` property: +/// +/// ```swift +/// gradientLoadingBar.isHidden = false +/// gradientLoadingBar.isHidden = true +/// ``` +/// +/// ## Thread Safety +/// +/// This class is marked with `@MainActor` and must be accessed from the main thread. +/// +/// - SeeAlso: ``GradientActivityIndicatorView`` for the underlying view that renders the gradient. +@MainActor +public class GradientLoadingBarController { + + // MARK: - Public Properties + + /// The shared singleton instance of the gradient loading bar. + /// + /// You can customize the shared instance's appearance at any time: + /// ```swift + /// GradientLoadingBar.shared.gradientColors = [.systemIndigo, .systemPurple, .systemPink] + /// ``` + public static var shared = GradientLoadingBar() + + /// The colors used for the gradient. + public var gradientColors: [UIColor] { + get { gradientActivityIndicatorView.gradientColors } + set { gradientActivityIndicatorView.gradientColors = newValue } + } + + /// The duration for one complete animation cycle, measured in seconds. + public var progressAnimationDuration: TimeInterval { + get { gradientActivityIndicatorView.progressAnimationDuration } + set { gradientActivityIndicatorView.progressAnimationDuration = newValue } + } + + /// A Boolean value that determines whether the loading bar is hidden. + /// + /// Set this property to `false` to show the loading bar immediately (without animation), + /// or to `true` to hide it immediately. + /// + /// For animated visibility changes, use ``fadeIn(duration:completion:)`` or + /// ``fadeOut(duration:completion:)`` instead. + public var isHidden: Bool { + get { gradientActivityIndicatorView.isHidden } + set { gradientActivityIndicatorView.isHidden = newValue } + } + + // MARK: - Internal Properties + + /// The view containing the gradient layer. + /// + /// - Note: This property needs to have an internal access level to allow + /// accessing this view in ``NotchGradientLoadingBarController``. + let gradientActivityIndicatorView = GradientActivityIndicatorView() + + /// The height of the gradient bar. + /// + /// - Note: This property needs to have an internal access level to allow + /// accessing this value in ``NotchGradientLoadingBarController``. + let height: CGFloat + + // MARK: - Private Properties + + /// Boolean flag whether the top layout constraint should respect the `safeAreaLayoutGuide`. + private let isRelativeToSafeArea: Bool + + /// The view model for our controller. + private let viewModel: GradientLoadingBarViewModel + + /// The dispose bag for the observables. + private var subscriptions = Set() + + // MARK: - Instance Lifecycle + + /// Internal initializer for dependency injection. + /// This allows snapshot testing the controller with a mocked view model. + init( + height: CGFloat, + isRelativeToSafeArea: Bool, + gradientLoadingBarViewModel viewModel: GradientLoadingBarViewModel, + ) { + self.height = height + self.isRelativeToSafeArea = isRelativeToSafeArea + self.viewModel = viewModel + + bindViewModelToView() + + // We don't want the view to be visible initially. + isHidden = true + } + + /// Creates a new gradient loading bar controller. + /// + /// - Parameters: + /// - height: The height of the gradient bar in points. + /// - isRelativeToSafeArea: Boolean flag whether the top layout constraint should respect the `safeAreaLayoutGuide`. + public convenience init( + height: CGFloat = .GradientLoadingBar.height, + isRelativeToSafeArea: Bool = true, + ) { + self.init( + height: height, + isRelativeToSafeArea: isRelativeToSafeArea, + gradientLoadingBarViewModel: ViewModel(), + ) + } + + @MainActor + deinit { + gradientActivityIndicatorView.removeFromSuperview() + } + + // MARK: - Public Methods + + /// Animates the gradient loading bar from hidden to fully visible. + /// + /// - Parameters: + /// - duration: The animation duration, in seconds. + /// - completion: An optional closure invoked when the animation ends. + /// The Boolean parameter indicates whether the animation + /// completed successfully (`true`) or was interrupted (`false`). + public func fadeIn( + duration: TimeInterval = .GradientLoadingBar.fadeInDuration, + completion: ((Bool) -> Void)? = nil, + ) { + gradientActivityIndicatorView.fadeIn( + duration: duration, + completion: completion, + ) + } + + /// Animates the gradient loading bar from visible to fully transparent. + /// + /// - Parameters: + /// - duration: The animation duration, in seconds. + /// - completion: An optional closure invoked when the animation ends. + /// The Boolean parameter indicates whether the animation + /// completed successfully (`true`) or was interrupted (`false`). + public func fadeOut( + duration: TimeInterval = .GradientLoadingBar.fadeOutDuration, + completion: ((Bool) -> Void)? = nil, + ) { + gradientActivityIndicatorView.fadeOut( + duration: duration, + completion: completion, + ) + } + + // MARK: - Internal Methods + + /// Applies the layout constraints for the `GradientActivityIndicatorView`. + /// + /// - Note: This method needs to have an internal access level to allow + /// overwriting it in ``NotchGradientLoadingBarController``. + func setupConstraints(superview: UIView) { + let superViewTopAnchor = if isRelativeToSafeArea { + superview.safeAreaLayoutGuide.topAnchor + } else { + superview.topAnchor + } + + NSLayoutConstraint.activate([ + gradientActivityIndicatorView.topAnchor.constraint(equalTo: superViewTopAnchor), + gradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: height), + + gradientActivityIndicatorView.leadingAnchor.constraint(equalTo: superview.leadingAnchor), + gradientActivityIndicatorView.trailingAnchor.constraint(equalTo: superview.trailingAnchor), + ]) + } + + // MARK: - Private Methods + + private func bindViewModelToView() { + viewModel.superview.sink { [weak self] superview in + self?.updateSuperview(superview) + }.store(in: &subscriptions) + } + + private func updateSuperview(_ superview: UIView?) { + // If the views superview is not `nil`, the superview releases the view. + gradientActivityIndicatorView.removeFromSuperview() + + if let superview { + gradientActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false + superview.addSubview(gradientActivityIndicatorView) + + setupConstraints(superview: superview) + } + } +} diff --git a/Sources/GradientLoadingBar/Features/GradientLoadingBarView/GradientLoadingBarView+ViewModel.swift b/Sources/GradientLoadingBar/Features/GradientLoadingBarView/GradientLoadingBarView+ViewModel.swift new file mode 100644 index 0000000..39b1b80 --- /dev/null +++ b/Sources/GradientLoadingBar/Features/GradientLoadingBarView/GradientLoadingBarView+ViewModel.swift @@ -0,0 +1,94 @@ +// +// GradientLoadingBarView+ViewModel.swift +// GradientLoadingBar +// +// Created by Felix Mau on 21.03.22. +// Copyright © 2022 Felix Mau. All rights reserved. +// + +import Observation +import SwiftUI + +extension GradientLoadingBarView { + + /// This view model contains all logic related to the `GradientLoadingBarView` + /// and the corresponding progress animation. + @Observable + final class ViewModel: ObservableObject { + + // MARK: - Internal Properties + + /// The gradient colors used for the progress animation, + /// including the reversed colors to simulate an infinite animation. + let gradientColors: [Color] + + /// The horizontal offset of the `LinearGradient` used to simulate the progress animation. + private(set) var gradientOffset: CGFloat = 0 + + /// The current size of the `GradientLoadingBarView`. + var size: CGSize = .zero { + didSet { + // This will stop any ongoing animation. + // Source: https://stackoverflow.com/a/59150940 + withAnimation(.linear(duration: 0)) { + gradientOffset = -size.width + } + + let progressAnimation: Animation = .linear(duration: progressDuration).repeatForever(autoreverses: false) + withAnimation(progressAnimation) { + gradientOffset = size.width + } + } + } + + /// The width of the `LinearGradient`. + var gradientWidth: CGFloat { + // To fit the `gradientColors + reversedGradientColors + gradientColors` in our view, + // we have to apply three times the width of our parent view. + size.width * 3 + } + + // MARK: - Private Properties + + private let progressDuration: TimeInterval + + // MARK: - Instance Lifecycle + + init(gradientColors: [Color], progressDuration: TimeInterval) { + self.gradientColors = gradientColors.infiniteGradientColors() + self.progressDuration = progressDuration + } + } +} + +// MARK: - Helper + +private extension [Color] { + /// Returns a color sequence suitable for a seamless, looping gradient animation. + /// + /// The resulting array mirrors the colors back and forth so the gradient can animate + /// continuously without visible seams or duplicated colors at the turning points. + /// + /// To avoid duplicate colors at the inner edges, the reversed sequence omits + /// the first and last elements before being appended. + /// + /// Example: + /// ``` + /// [.red, .yellow, .green] + /// → [.red, .yellow, .green, .yellow, .red, .yellow, .green] + /// + /// [.red, .yellow, .green, .blue] + /// → [.red, .yellow, .green, .blue, .green, .yellow, .red, .yellow, .green, .blue] + /// ``` + /// + /// - Returns: A color array that can be animated left-to-right and looped without a visible jump. + func infiniteGradientColors() -> [Color] { + guard count > 1 else { return self } + + let reversedColors = reversed() + .dropFirst() + .dropLast() + + return self + reversedColors + self + } +} diff --git a/Sources/GradientLoadingBar/Features/GradientLoadingBarView/GradientLoadingBarView.swift b/Sources/GradientLoadingBar/Features/GradientLoadingBarView/GradientLoadingBarView.swift new file mode 100644 index 0000000..1117ba4 --- /dev/null +++ b/Sources/GradientLoadingBar/Features/GradientLoadingBarView/GradientLoadingBarView.swift @@ -0,0 +1,108 @@ +// +// GradientLoadingBarView.swift +// GradientLoadingBar +// +// Created by Felix Mau on 08.03.22. +// Copyright © 2022 Felix Mau. All rights reserved. +// + +import SwiftUI +import UIKit + +/// A SwiftUI view that displays an animated horizontal gradient, commonly used as a loading indicator. +/// +/// ## Overview +/// +/// `GradientLoadingBarView` renders a customizable color gradient that continuously animates from left to right, +/// creating an infinite scrolling effect. +/// +/// ## Usage +/// +/// ```swift +/// struct ContentView: some View { +/// +/// var body: some View { +/// GradientLoadingBarView() +/// .frame(maxWidth: .infinity, maxHeight: 3) +/// .cornerRadius(1.5) +/// } +/// } +/// ``` +/// +/// ## Configuration +/// +/// You can customize the gradient colors and animation speed: +/// +/// ```swift +/// GradientLoadingBarView( +/// gradientColors: [.indigo, .purple, .pink], +/// progressDuration: 2.0 +/// ) +/// ``` +/// +/// ## Accessibility +/// +/// The view is automatically hidden from accessibility technologies, +/// as it serves as a visual indicator only. +/// +/// - Note: The animation starts automatically when the view appears and continues indefinitely. +/// Control visibility either by conditionally including the view in your hierarchy or by +/// applying the `opacity(_:)` or `hidden(_:)` view modifier. +/// +/// - SeeAlso: ``GradientActivityIndicatorView`` for the UIKit equivalent. +public struct GradientLoadingBarView: View { + + // MARK: - Private Properties + + @State + private var viewModel: ViewModel + + // MARK: - Initializer + + /// Creates a new gradient loading bar view. + /// + /// - Parameters: + /// - gradientColors: The colors used for the gradient. + /// - progressDuration: The duration for one complete animation cycle, measured in seconds. + public init( + gradientColors: [Color] = Color.GradientLoadingBar.gradientColors, + progressDuration: TimeInterval = .GradientLoadingBar.progressDuration, + ) { + viewModel = ViewModel( + gradientColors: gradientColors, + progressDuration: progressDuration, + ) + } + + // MARK: - Render + + public var body: some View { + Color.clear + .onGeometryChange(for: CGSize.self) { proxy in + proxy.size + } action: { + // Restart a possible animation whenever the size changes. + viewModel.size = $0 + } + // Using an `overlay` here makes sure that the parent view won't change it's frame. + .overlay( + LinearGradient( + colors: viewModel.gradientColors, + startPoint: .leading, + endPoint: .trailing, + ) + .frame(width: viewModel.gradientWidth) + .offset(x: viewModel.gradientOffset, y: 0), + ) + .clipped() + .accessibilityHidden(true) + } +} + +// MARK: - Preview + +#Preview { + GradientLoadingBarView() + .frame(maxWidth: .infinity, maxHeight: .GradientLoadingBar.height) + .padding(24) +} diff --git a/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/Model/NotchDevice.swift b/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/Model/NotchDevice.swift new file mode 100644 index 0000000..34e8702 --- /dev/null +++ b/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/Model/NotchDevice.swift @@ -0,0 +1,34 @@ +// +// NotchDevice.swift +// GradientLoadingBar +// +// Created by Felix Mau on 18.01.26. +// Copyright © 2026 Felix Mau. All rights reserved. +// + +/// iPhone models that feature a notch (TrueDepth camera housing) at the top of the display. +/// +/// - Note: iPhone 14 Pro and later models use the Dynamic Island instead of a notch +/// and are therefore not included in this list. +enum NotchDevice: String, CaseIterable { + // These devices are excluded because they don't support the minimum required iOS version (iOS 26). + // https://support.apple.com/guide/iphone/iphone-models-compatible-with-ios-26-iphe3fa5df43/ios + // case iPhoneX + // case iPhoneXS + // case iPhoneXSMax + // case iPhoneXR + + case iPhone11 + case iPhone11Pro + case iPhone11ProMax + case iPhone12Mini + case iPhone12 + case iPhone12Pro + case iPhone12ProMax + case iPhone13Mini + case iPhone13 + case iPhone13Pro + case iPhone13ProMax + case iPhone14 + case iPhone14Plus +} diff --git a/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/Model/NotchLayout.swift b/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/Model/NotchLayout.swift new file mode 100644 index 0000000..34932c6 --- /dev/null +++ b/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/Model/NotchLayout.swift @@ -0,0 +1,37 @@ +// +// NotchLayout.swift +// GradientLoadingBar +// +// Created by Felix Mau on 18.01.26. +// Copyright © 2026 Felix Mau. All rights reserved. +// + +import Foundation + +/// Layout configuration for rendering a gradient loading bar that wraps around the iPhone notch. +/// +/// This struct defines the geometric parameters needed to construct a bezier path +/// that follows the curved shape of the notch on different iPhone models. +/// +/// For a visual explanation of the notch geometry, see the graphic at `Assets/iphone-x-screen-demystified.svg` or +/// the article “iPhone X Screen Demystified” at . +struct NotchLayout { + /// The width of the iPhone notch. + let notchWidth: CGFloat + + /// The radius of the small circle on the outside of the notch. + /// - Note: In the referenced graphic above, this circle has a radius of `6pt`. + let smallCircleRadius: CGFloat + + /// The radius of the large circle on the inside of the notch. + /// - Note: In the referenced graphic above, this circle has a radius of `20pt`. + let largeCircleRadius: CGFloat + + /// Vertical offset for the center-point of the large circle: + /// - A positive value will move the large circles downwards. + /// - A negative offset will move them upwards. + let largeCircleVerticalOffset: CGFloat + + /// The transform to be applied to the entire bezier path. + let transform: CGAffineTransform +} diff --git a/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/NotchGradientLoadingBarController+ViewModel.swift b/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/NotchGradientLoadingBarController+ViewModel.swift new file mode 100644 index 0000000..ddc1b85 --- /dev/null +++ b/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/NotchGradientLoadingBarController+ViewModel.swift @@ -0,0 +1,109 @@ +// +// NotchGradientLoadingBarController+ViewModel.swift +// GradientLoadingBar +// +// Created by Felix Mau on 03.10.21. +// Copyright © 2021 Felix Mau. All rights reserved. +// + +import UIKit + +/// A protocol that abstracts the view model for dependency injection. +/// This enables snapshot testing the controller with all notch devices. +@MainActor +protocol NotchGradientLoadingBarViewModel: AnyObject { + /// The notch device type if the current device has a notch, otherwise `nil`. + var notchDevice: NotchDevice? { get } +} + +extension NotchGradientLoadingBarController { + + /// This view model determines if the current device has a notch, + /// and provides related information. + @MainActor + final class ViewModel: NotchGradientLoadingBarViewModel { + + // MARK: - Internal Properties + + /// The current device if it has a notch / otherwise `nil`. + let notchDevice: NotchDevice? + + // MARK: - Instance Lifecycle + + init(deviceIdentifier: String = UIDevice.identifier) { + notchDevice = NotchDevice(deviceIdentifier: deviceIdentifier) + } + } +} + +// MARK: - Helper + +private extension NotchDevice { + /// Creates a notch device from the given hardware model identifier. + /// + /// - Parameter deviceIdentifier: The device's hardware model identifier (e.g. "iPhone14,5" for iPhone 13). + /// + /// - Returns: The corresponding `NotchDevice`, or `nil` if the identifier doesn't match a known notch device. + /// + /// - SeeAlso: + init?(deviceIdentifier: String) { + // swiftlint:disable:previous cyclomatic_complexity + switch deviceIdentifier { + case "iPhone12,1": + self = .iPhone11 + case "iPhone12,3": + self = .iPhone11Pro + case "iPhone12,5": + self = .iPhone11ProMax + case "iPhone13,1": + self = .iPhone12Mini + case "iPhone13,2": + self = .iPhone12 + case "iPhone13,3": + self = .iPhone12Pro + case "iPhone13,4": + self = .iPhone12ProMax + case "iPhone14,4": + self = .iPhone13Mini + case "iPhone14,5": + self = .iPhone13 + case "iPhone14,2": + self = .iPhone13Pro + case "iPhone14,3": + self = .iPhone13ProMax + case "iPhone14,7": + self = .iPhone14 + case "iPhone14,8": + self = .iPhone14Plus + default: + return nil + } + } +} + +private extension UIDevice { + /// The hardware model identifier for the current device (e.g. "iPhone14,5" for iPhone 13). + /// + /// On the Simulator, this property returns the identifier of the simulated device. + /// + /// - SeeAlso: + /// - + /// - + static var identifier: String { + #if targetEnvironment(simulator) + return ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "" + #else + var systemInfo = utsname() + uname(&systemInfo) + + let machineMirror = Mirror(reflecting: systemInfo.machine) + return machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { + return identifier + } + + return identifier + String(UnicodeScalar(UInt8(value))) + } + #endif + } +} diff --git a/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/NotchGradientLoadingBarController.swift b/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/NotchGradientLoadingBarController.swift new file mode 100644 index 0000000..c73c3e5 --- /dev/null +++ b/Sources/GradientLoadingBar/Features/NotchGradientLoadingBarController/NotchGradientLoadingBarController.swift @@ -0,0 +1,452 @@ +// +// NotchGradientLoadingBarController.swift +// GradientLoadingBar +// +// Created by Felix Mau on 06.11.20. +// Copyright © 2020 Felix Mau. All rights reserved. +// + +import UIKit + +/// Type-alias for the controller to match the package name. +public typealias NotchGradientLoadingBar = NotchGradientLoadingBarController + +/// A gradient loading bar controller that wraps around the iPhone notch. +/// +/// ## Overview +/// +/// `NotchGradientLoadingBarController` is a subclass of ``GradientLoadingBarController`` +/// that renders the gradient loading bar following the curved shape of the iPhone notch. +/// On devices without a notch, it falls back to the standard loading bar behavior. +/// +/// ## Usage +/// +/// The usage is identical to ``GradientLoadingBarController``. +/// Please have a look at its documentation for usage examples. +/// +/// ## Supported Devices +/// +/// The following iPhone models with notches are supported: +/// - iPhone 11, 11 Pro, 11 Pro Max +/// - iPhone 12 Mini, 12, 12 Pro, 12 Pro Max +/// - iPhone 13 Mini, 13, 13 Pro, 13 Pro Max +/// - iPhone 14, 14 Plus +/// +/// - Note: iPhone 14 Pro and later models use the Dynamic Island instead of a notch and are +/// not supported by this controller. +/// +/// ## Portrait Mode Only +/// +/// The notch geometry is currently optimized for portrait orientation only. +/// Device rotation is not supported! +/// +/// ## Thread Safety +/// +/// This class is marked with `@MainActor` and must be accessed from the main thread. +/// +/// - SeeAlso: +/// - ``GradientLoadingBarController`` for the base implementation without notch support. +/// - ``GradientActivityIndicatorView`` for the underlying view that renders the gradient. +@MainActor +public class NotchGradientLoadingBarController: GradientLoadingBarController { + + // MARK: - Private Properties + + /// The view model for our controller. + private let viewModel: NotchGradientLoadingBarViewModel + + // MARK: - Instance Lifecycle + + /// Internal initializer for dependency injection. + /// This allows snapshot testing the controller with a mocked view model. + init( + height: CGFloat, + isRelativeToSafeArea: Bool, + gradientLoadingBarViewModel: GradientLoadingBarViewModel, + notchGradientLoadingBarViewModel viewModel: NotchGradientLoadingBarViewModel, + ) { + self.viewModel = viewModel + + super.init( + height: height, + isRelativeToSafeArea: isRelativeToSafeArea, + gradientLoadingBarViewModel: gradientLoadingBarViewModel, + ) + } + + /// Creates a new notch-aware gradient loading bar controller. + /// + /// - Parameters: + /// - height: The height of the gradient bar in points. + /// - isRelativeToSafeArea: Boolean flag whether the top layout constraint should respect the `safeAreaLayoutGuide`. + public convenience init( + height: CGFloat = .GradientLoadingBar.height, + isRelativeToSafeArea: Bool = true, + ) { + self.init( + height: height, + isRelativeToSafeArea: isRelativeToSafeArea, + gradientLoadingBarViewModel: GradientLoadingBarController.ViewModel(), + notchGradientLoadingBarViewModel: NotchGradientLoadingBarController.ViewModel(), + ) + } + + // MARK: - Internal Methods + + override func setupConstraints(superview: UIView) { + guard let notchDevice = viewModel.notchDevice else { + // No special masking required for devices without a notch. + super.setupConstraints(superview: superview) + return + } + + // We currently only support portrait mode (without device rotation), + // and therefore can safely use `bounds.size.width` here. + let screenWidth = superview.bounds.size.width + let notchLayout = NotchLayout(notchDevice: notchDevice) + let notchBezierPath: UIBezierPath = .notchBezierPath( + screenWidth: screenWidth, + notchLayout: notchLayout, + height: height, + ) + + let notchBezierPathSize = notchBezierPath.bounds.size + NSLayoutConstraint.activate([ + gradientActivityIndicatorView.leadingAnchor.constraint(equalTo: superview.leadingAnchor), + gradientActivityIndicatorView.trailingAnchor.constraint(equalTo: superview.trailingAnchor), + gradientActivityIndicatorView.topAnchor.constraint(equalTo: superview.topAnchor), + gradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: notchBezierPathSize.height), + ]) + + maskView(with: notchBezierPath) + } + + // MARK: - Private Methods + + private func maskView(with bezierPath: UIBezierPath) { + let shapeLayer = CAShapeLayer() + shapeLayer.path = bezierPath.cgPath + shapeLayer.fillColor = UIColor.white.cgColor + shapeLayer.cornerCurve = .continuous + + gradientActivityIndicatorView.layer.mask = shapeLayer + } +} + +// MARK: - Helper + +private extension NotchLayout { + /// Initializes the notch layout for the given notch device. + /// + /// For a visual explanation of the notch geometry, see the graphic at `Assets/iphone-x-screen-demystified.svg` or + /// the article “iPhone X Screen Demystified” at . + init(notchDevice: NotchDevice) { + // swiftlint:disable:previous function_body_length + switch notchDevice { + case .iPhone11: + self.init( + notchWidth: 230, + smallCircleRadius: 6, + largeCircleRadius: 24, + largeCircleVerticalOffset: -3.5, + transform: .identity, + ) + + case .iPhone11Pro: + self.init( + notchWidth: 208, + smallCircleRadius: 6, + largeCircleRadius: 21, + largeCircleVerticalOffset: -3.5, + transform: + // At least in the Simulator, the notch doesn't seem to be perfectly centered. + CGAffineTransform(translationX: 0.35, y: 0), + ) + + case .iPhone11ProMax: + self.init( + notchWidth: 209, + smallCircleRadius: 6, + largeCircleRadius: 21, + largeCircleVerticalOffset: -3.5, + transform: .identity, + ) + + case .iPhone12Mini: + self.init( + notchWidth: 226, + smallCircleRadius: 6, + largeCircleRadius: 24, + largeCircleVerticalOffset: -2, + transform: .identity, + ) + + case .iPhone12, .iPhone12Pro: + self.init( + notchWidth: 209.75, + smallCircleRadius: 6, + largeCircleRadius: 21, + largeCircleVerticalOffset: -1.75, + transform: .identity, + ) + + case .iPhone12ProMax: + self.init( + notchWidth: 209, + smallCircleRadius: 6, + largeCircleRadius: 21, + largeCircleVerticalOffset: -1.75, + transform: .identity, + ) + + case .iPhone13Mini: + self.init( + notchWidth: 174.75, + smallCircleRadius: 6, + largeCircleRadius: 24.5, + largeCircleVerticalOffset: 0.5, + transform: .identity, + ) + + case .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14, .iPhone14Plus: + // ‟iPhone 13 notch is 20% smaller in width, but it is also a little taller in height‟. + // Source: https://9to5mac.com/2021/09/14/iphone-13-notch-smaller-but-bigger + self.init( + notchWidth: 161.25, + smallCircleRadius: 6, + largeCircleRadius: 22, + largeCircleVerticalOffset: -1, + transform: .identity, + ) + } + } + + /// The diameter of the small circle. + var smallCircleDiameter: CGFloat { + 2 * smallCircleRadius + } +} + +private extension UIBezierPath { + /// Creates a Bezier path that describes the shape of the iPhone notch. + /// + /// - Parameters: + /// - screenWidth: The width of the screen, in points. + /// - notchLayout: The layout configuration that defines the geometry of the notch. + /// - height: The vertical distance by which the path extends downward. + /// + /// For a visual explanation of the notch geometry, see the graphic at `Assets/iphone-x-screen-demystified.svg` or + /// the article “iPhone X Screen Demystified” at . + static func notchBezierPath( + // swiftlint:disable:previous function_body_length + screenWidth: CGFloat, + notchLayout: NotchLayout, + height: CGFloat, + ) -> UIBezierPath { + // We calculate the coordinates from the the top center point of the screen. + // Doing so centers the entire notch bezier path horizontally. + let topCenterPoint = CGPoint( + x: screenWidth / 2, + y: 0, + ) + + // The center point of the small circle on the left side. + // This circle has a `6pt` radius in the referenced graphic. + let leftSmallCircleCenterPoint = CGPoint( + x: topCenterPoint.x - notchLayout.notchWidth / 2 - notchLayout.smallCircleRadius, + y: notchLayout.smallCircleRadius, + ) + + // The center point of the large circle on the left side. + // This circle has a `20pt` radius in the referenced graphic. + let leftLargeCircleCenterPoint = CGPoint( + x: topCenterPoint.x - notchLayout.notchWidth / 2 + notchLayout.largeCircleRadius, + y: notchLayout.smallCircleDiameter + notchLayout.largeCircleVerticalOffset, + ) + + // The center point of the small circle on the right side. + // This circle has a `6pt` radius in the referenced graphic. + let rightSmallCircleCenterPoint = CGPoint( + x: topCenterPoint.x + notchLayout.notchWidth / 2 + notchLayout.smallCircleRadius, + y: notchLayout.smallCircleRadius, + ) + + // The center point of the large circle on the right side. + // This circle has a `20pt` radius in the referenced graphic. + let rightLargeCircleCenterPoint = CGPoint( + x: topCenterPoint.x + notchLayout.notchWidth / 2 - notchLayout.largeCircleRadius, + y: notchLayout.smallCircleDiameter + notchLayout.largeCircleVerticalOffset, + ) + + let bezierPath = UIBezierPath() + bezierPath.move(to: .zero) + + // Draw line to the top center point of the left small circle. + bezierPath.addLine( + x: leftSmallCircleCenterPoint.x, + y: 0, + ) + + // Draw the top right quarter of the of the left small circle. + bezierPath.addArc( + withCenter: leftSmallCircleCenterPoint, + radius: notchLayout.smallCircleRadius, + startAngle: Angle.top.radians, + endAngle: Angle.right.radians, + clockwise: true, + ) + + // Draw the bottom left quarter of the left large circle. + bezierPath.addArc( + withCenter: leftLargeCircleCenterPoint, + radius: notchLayout.largeCircleRadius, + startAngle: Angle.left.radians, + endAngle: Angle.bottom.radians, + clockwise: false, + ) + + // Draw line to the bottom center point of the right large circle. + bezierPath.addLine( + x: rightLargeCircleCenterPoint.x, + y: rightLargeCircleCenterPoint.y + notchLayout.largeCircleRadius, + ) + + // Draw the bottom right quarter of the right large circle. + bezierPath.addArc( + withCenter: rightLargeCircleCenterPoint, + radius: notchLayout.largeCircleRadius, + startAngle: Angle.bottom.radians, + endAngle: Angle.right.radians, + clockwise: false, + ) + + // Draw the top left quarter of the right small circle. + bezierPath.addArc( + withCenter: rightSmallCircleCenterPoint, + radius: notchLayout.smallCircleRadius, + startAngle: Angle.left.radians, + endAngle: Angle.top.radians, + clockwise: true, + ) + + // Draw line to the end of the screen. + bezierPath.addLine(x: screenWidth, y: 0) + + // On the return path, the circles reuse the original center point, but their radius must be adjusted. + // + // For nested circles, the outer radius is calculated as the inner radius plus the padding between them. + // This ensures proper alignment of the borders. + // + // ``` + // outerRadius = innerRadius + padding + // ``` + // + // https://www.30secondsofcode.org/css/s/nested-border-radius/ + + // Move line down by the given height. + bezierPath.addLine( + x: screenWidth, + y: height, + ) + + // Draw line to the top center point of the right small circle. + bezierPath.addLine( + x: height + rightSmallCircleCenterPoint.x, + y: height, + ) + + // Draw the top left quarter of the right small circle. + bezierPath.addArc( + withCenter: rightSmallCircleCenterPoint, + radius: notchLayout.smallCircleRadius - height, + startAngle: Angle.top.radians, + endAngle: Angle.left.radians, + clockwise: false, + ) + + // Draw the bottom right quarter of the right large circle. + bezierPath.addArc( + withCenter: rightLargeCircleCenterPoint, + radius: notchLayout.largeCircleRadius + height, + startAngle: Angle.right.radians, + endAngle: Angle.bottom.radians, + clockwise: true, + ) + + // Draw line to the bottom center point of the left large circle. + bezierPath.addLine( + x: height + leftLargeCircleCenterPoint.x, + y: height + leftLargeCircleCenterPoint.y + notchLayout.largeCircleRadius, + ) + + // Draw the bottom left quarter of the left large circle. + bezierPath.addArc( + withCenter: leftLargeCircleCenterPoint, + radius: notchLayout.largeCircleRadius + height, + startAngle: Angle.bottom.radians, + endAngle: Angle.left.radians, + clockwise: true, + ) + + // Draw the top right quarter of the left small circle. + bezierPath.addArc( + withCenter: leftSmallCircleCenterPoint, + radius: notchLayout.smallCircleRadius - height, + startAngle: Angle.right.radians, + endAngle: Angle.top.radians, + clockwise: false, + ) + + // Draw line to the beginning of the screen. + bezierPath.addLine(x: 0, y: height) + bezierPath.close() + + // Some notches require a slight transform to perfectly align. + bezierPath.apply(notchLayout.transform) + return bezierPath + } + + /// Adds a line to the specified `x` and `y` coordinates (syntactic sugar). + func addLine(x: CGFloat, y: CGFloat) { + // swiftlint:disable:previous identifier_name + addLine(to: CGPoint(x: x, y: y)) + } +} + +// MARK: - Supporting Types + +/// Represents cardinal directions as angles in UIKit’s coordinate system. +/// +/// This enum is intended for use with `UIBezierPath` and other UIKit/Core Graphics +/// APIs that work with radians. +/// +/// ### Coordinate system notes +/// - `0` radians points to the **right** (positive X axis) +/// - Angles increase **clockwise** +/// - The Y axis points **downward**, unlike standard Cartesian math +/// +/// These conventions match `UIBezierPath(addArc:radius:startAngle:endAngle:clockwise:)`. +/// +/// - SeeAlso: +private enum Angle { + case right + case bottom + case left + case top + + /// The angle value in radians, suitable for UIKit drawing APIs. + var radians: CGFloat { + switch self { + case .right: + 0 + case .bottom: + .pi / 2 + case .left: + .pi + case .top: + .pi * (3 / 2.0) + } + } +} + +// swiftlint:disable:this file_length diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/GradientActivityIndicatorViewSnapshotTests.swift b/Tests/GradientLoadingBarTests/SnapshotTests/GradientActivityIndicatorViewSnapshotTests.swift new file mode 100644 index 0000000..f565098 --- /dev/null +++ b/Tests/GradientLoadingBarTests/SnapshotTests/GradientActivityIndicatorViewSnapshotTests.swift @@ -0,0 +1,55 @@ +// +// GradientActivityIndicatorViewSnapshotTests.swift +// GradientLoadingBarTests +// +// Created by Felix Mau on 09.11.19. +// Copyright © 2019 Felix Mau. All rights reserved. +// + +import SnapshotTesting +import Testing +import UIKit +@testable import GradientLoadingBar + +/// - Note: For more readable reference file names, we don't use raw identifiers for test names here. +@MainActor +@Suite +struct GradientActivityIndicatorViewSnapshotTests { + // swiftlint:disable:previous type_name + + // MARK: - Config + + private enum Config { + /// The frame we use for rendering the `GradientActivityIndicatorView`. + /// This will also be the image size of our reference image. + static let frame = CGRect(origin: .zero, size: CGSize(width: 375, height: 4)) + + /// The custom colors we use on this test-case. + /// + /// Source: + static let gradientColors = [ + #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), + ] + } + + // MARK: - Tests + + @Test + func gradientActivityIndicatorView_withDefaultGradientColors() { + // When + let gradientActivityIndicatorView = GradientActivityIndicatorView(frame: Config.frame) + + // Then + assertSnapshot(of: gradientActivityIndicatorView, as: .image) + } + + @Test + func gradientActivityIndicatorView_withCustomGradientColors() { + // When + let gradientActivityIndicatorView = GradientActivityIndicatorView(frame: Config.frame) + gradientActivityIndicatorView.gradientColors = Config.gradientColors + + // Then + assertSnapshot(of: gradientActivityIndicatorView, as: .image) + } +} diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/GradientLoadingBarControllerSnapshotests.swift b/Tests/GradientLoadingBarTests/SnapshotTests/GradientLoadingBarControllerSnapshotests.swift new file mode 100644 index 0000000..adb0769 --- /dev/null +++ b/Tests/GradientLoadingBarTests/SnapshotTests/GradientLoadingBarControllerSnapshotests.swift @@ -0,0 +1,100 @@ +// +// GradientLoadingBarControllerSnapshotests.swift +// GradientLoadingBarTests +// +// Created by Felix Mau on 14.06.20. +// Copyright © 2020 Felix Mau. All rights reserved. +// + +import Combine +import SnapshotTesting +import Testing +import UIKit +@testable import GradientLoadingBar + +/// - Note: For more readable reference file names, we don't use raw identifiers for test names here. +@MainActor +@Suite +struct GradientLoadingBarControllerSnapshotTests { + // swiftlint:disable:previous type_name + + // MARK: - Config + + private enum Config { + /// The frame we use for rendering the `GradientLoadingBarController`. + /// This will also be the image size of our reference image. + /// + /// - Note: We explicitly don't simulate a full device height here, to avoid unnecessary large images. + static let frame = CGRect(origin: .zero, size: CGSize(width: 375, height: 100)) + + /// The safe area insets we use on this test-case. + /// + /// Source: + static let safeAreaInset = UIEdgeInsets(top: 62, left: 0, bottom: 34, right: 0) + } + + // MARK: - Tests + + @Test + func gradientLoadingBarController_withIsRelativeToSafeAreaFalse() { + // Given + let mockSafeAreaView = MockSafeAreaView(frame: Config.frame) + mockSafeAreaView.stubbedSafeAreaInsets = Config.safeAreaInset + + let mockGradientLoadingBarViewModel = MockGradientLoadingBarViewModel( + superview: Just(mockSafeAreaView).eraseToAnyPublisher(), + ) + + // When + let controller = GradientLoadingBarController( + height: .GradientLoadingBar.height, + isRelativeToSafeArea: false, + gradientLoadingBarViewModel: mockGradientLoadingBarViewModel, + ) + controller.isHidden = false + + // Then + assertSnapshot(of: mockSafeAreaView, as: .image) + } + + @Test + func gradientLoadingBarController_withIsRelativeToSafeAreaTrue() { + // Given + let mockSafeAreaView = MockSafeAreaView(frame: Config.frame) + mockSafeAreaView.stubbedSafeAreaInsets = Config.safeAreaInset + + let mockGradientLoadingBarViewModel = MockGradientLoadingBarViewModel( + superview: Just(mockSafeAreaView).eraseToAnyPublisher(), + ) + + // When + let controller = GradientLoadingBarController( + height: .GradientLoadingBar.height, + isRelativeToSafeArea: true, + gradientLoadingBarViewModel: mockGradientLoadingBarViewModel, + ) + controller.isHidden = false + + // Then + assertSnapshot(of: mockSafeAreaView, as: .image) + } +} + +// MARK: - Supporting Types + +private final class MockGradientLoadingBarViewModel: GradientLoadingBarViewModel { + var superview: AnyPublisher + + init(superview: AnyPublisher) { + self.superview = superview + } +} + +/// A `UIView` subclass that allows manual control of `safeAreaInsets`. +private final class MockSafeAreaView: UIView { + var stubbedSafeAreaInsets: UIEdgeInsets = .zero + + override var safeAreaInsets: UIEdgeInsets { + stubbedSafeAreaInsets + } +} diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/GradientLoadingBarViewSnapshotTests.swift b/Tests/GradientLoadingBarTests/SnapshotTests/GradientLoadingBarViewSnapshotTests.swift new file mode 100644 index 0000000..e1f28e9 --- /dev/null +++ b/Tests/GradientLoadingBarTests/SnapshotTests/GradientLoadingBarViewSnapshotTests.swift @@ -0,0 +1,55 @@ +// +// GradientLoadingBarViewSnapshotTests.swift +// GradientLoadingBarTests +// +// Created by Felix Mau on 09.11.19. +// Copyright © 2019 Felix Mau. All rights reserved. +// + +import SnapshotTesting +import SwiftUI +import Testing +@testable import GradientLoadingBar + +/// - Note: For more readable reference file names, we don't use raw identifiers for test names here. +@MainActor +@Suite +struct GradientLoadingBarViewSnapshotTests { + + // MARK: - Config + + private enum Config { + /// The frame we use for rendering the `GradientLoadingBarView`. + /// This will also be the image size of our reference image. + static let frame = CGRect(origin: .zero, size: CGSize(width: 375, height: 4)) + + /// The custom colors we use on this test-case. + /// + /// Source: + static let gradientColors = [ + #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), + ].map(Color.init) + } + + // MARK: - Tests + + @Test + func gradientLoadingBarView_withDefaultGradientColors() { + // Given + let gradientLoadingBarView = GradientLoadingBarView() + .frame(width: Config.frame.width, height: Config.frame.height) + + // Then + assertSnapshot(of: gradientLoadingBarView, as: .image) + } + + @Test + func gradientLoadingBarView_withCustomGradientColors() { + // Given + let gradientLoadingBarView = GradientLoadingBarView(gradientColors: Config.gradientColors) + .frame(width: Config.frame.width, height: Config.frame.height) + + // Then + assertSnapshot(of: gradientLoadingBarView, as: .image) + } +} diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/NotchGradientLoadingBarControllerSnapshotTests.swift b/Tests/GradientLoadingBarTests/SnapshotTests/NotchGradientLoadingBarControllerSnapshotTests.swift new file mode 100644 index 0000000..bc4846e --- /dev/null +++ b/Tests/GradientLoadingBarTests/SnapshotTests/NotchGradientLoadingBarControllerSnapshotTests.swift @@ -0,0 +1,125 @@ +// +// NotchGradientLoadingBarControllerSnapshotTests.swift +// GradientLoadingBar +// +// Created by Felix Mau on 14.06.20. +// Copyright © 2020 Felix Mau. All rights reserved. +// + +import Combine +import SnapshotTesting +import Testing +import UIKit +@testable import GradientLoadingBar + +/// - Note: For more readable reference file names, we don't use raw identifiers for test names here. +@MainActor +@Suite +struct NotchGradientLoadingBarControllerSnapshotTests { + // swiftlint:disable:previous type_name + + // MARK: - Config + + private enum Config { + /// The frame we use for rendering the `GradientLoadingBarController`. + /// This will also be the image size of our reference image. + /// + /// - Note: We explicitly don't simulate a full device height here, to avoid unnecessary large images. + static let frame = CGRect(origin: .zero, size: CGSize(width: 375, height: 100)) + + /// The safe area insets we use on this test-case. + /// + /// Source: + static let safeAreaInset = UIEdgeInsets(top: 62, left: 0, bottom: 34, right: 0) + } + + // MARK: - Tests + + @Test(arguments: NotchDevice.allCases) + func notchGradientLoadingBarController_withNotchDevice(notchDevice: NotchDevice) { + // Given + let mockSafeAreaView = MockSafeAreaView(frame: Config.frame) + mockSafeAreaView.stubbedSafeAreaInsets = Config.safeAreaInset + + let mockGradientLoadingBarViewModel = MockGradientLoadingBarViewModel( + superview: Just(mockSafeAreaView).eraseToAnyPublisher(), + ) + + let mockNotchGradientLoadingBarViewModel = MockNotchGradientLoadingBarViewModel( + notchDevice: notchDevice, + ) + + // When + let controller = NotchGradientLoadingBarController( + height: .GradientLoadingBar.height, + isRelativeToSafeArea: false, + gradientLoadingBarViewModel: mockGradientLoadingBarViewModel, + notchGradientLoadingBarViewModel: mockNotchGradientLoadingBarViewModel, + ) + controller.isHidden = false + + // Then + assertSnapshot( + of: mockSafeAreaView, + as: .image, + named: notchDevice.rawValue, + ) + } + + @Test + func notchGradientLoadingBarController_withoutNotchDevice() { + // Given + let mockSafeAreaView = MockSafeAreaView(frame: Config.frame) + mockSafeAreaView.stubbedSafeAreaInsets = Config.safeAreaInset + + let mockGradientLoadingBarViewModel = MockGradientLoadingBarViewModel( + superview: Just(mockSafeAreaView).eraseToAnyPublisher(), + ) + + let mockNotchGradientLoadingBarViewModel = MockNotchGradientLoadingBarViewModel( + notchDevice: nil, + ) + + // When + let controller = NotchGradientLoadingBarController( + height: .GradientLoadingBar.height, + isRelativeToSafeArea: false, + gradientLoadingBarViewModel: mockGradientLoadingBarViewModel, + notchGradientLoadingBarViewModel: mockNotchGradientLoadingBarViewModel, + ) + controller.isHidden = false + + // Then + assertSnapshot( + of: mockSafeAreaView, + as: .image, + ) + } +} + +// MARK: - Supporting Types + +private final class MockNotchGradientLoadingBarViewModel: NotchGradientLoadingBarViewModel { + let notchDevice: NotchDevice? + + init(notchDevice: NotchDevice?) { + self.notchDevice = notchDevice + } +} + +private final class MockGradientLoadingBarViewModel: GradientLoadingBarViewModel { + var superview: AnyPublisher + + init(superview: AnyPublisher) { + self.superview = superview + } +} + +/// A `UIView` subclass that allows manual control of `safeAreaInsets`. +private final class MockSafeAreaView: UIView { + var stubbedSafeAreaInsets: UIEdgeInsets = .zero + + override var safeAreaInsets: UIEdgeInsets { + stubbedSafeAreaInsets + } +} diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/README.md b/Tests/GradientLoadingBarTests/SnapshotTests/README.md new file mode 100644 index 0000000..dd4a950 --- /dev/null +++ b/Tests/GradientLoadingBarTests/SnapshotTests/README.md @@ -0,0 +1,4 @@ +# Snapshot-Tests + +## Device +Run test-cases with the `iPhone 17` using `iOS 26.2`. diff --git a/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/__Snapshots__/GradientActivityIndicatorViewTestCase/test_gradientActivityIndicatorView_shouldContainCorrectCustomColors.1.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withCustomGradientColors.1.png similarity index 96% rename from GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/__Snapshots__/GradientActivityIndicatorViewTestCase/test_gradientActivityIndicatorView_shouldContainCorrectCustomColors.1.png rename to Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withCustomGradientColors.1.png index 3a4f5a5..065e8f9 100644 Binary files a/GradientLoadingBar/Tests/SnapshotTests/GradientActivityIndicatorView/__Snapshots__/GradientActivityIndicatorViewTestCase/test_gradientActivityIndicatorView_shouldContainCorrectCustomColors.1.png and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withCustomGradientColors.1.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withCustomGradientColors.2.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withCustomGradientColors.2.png new file mode 100644 index 0000000..065e8f9 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withCustomGradientColors.2.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withDefaultGradientColors.1.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withDefaultGradientColors.1.png new file mode 100644 index 0000000..b1d2e39 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withDefaultGradientColors.1.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withDefaultGradientColors.2.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withDefaultGradientColors.2.png new file mode 100644 index 0000000..b1d2e39 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientActivityIndicatorViewSnapshotTests/gradientActivityIndicatorView_withDefaultGradientColors.2.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarControllerSnapshotests/gradientLoadingBarController_withIsRelativeToSafeAreaFalse.1.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarControllerSnapshotests/gradientLoadingBarController_withIsRelativeToSafeAreaFalse.1.png new file mode 100644 index 0000000..b1d18e0 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarControllerSnapshotests/gradientLoadingBarController_withIsRelativeToSafeAreaFalse.1.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarControllerSnapshotests/gradientLoadingBarController_withIsRelativeToSafeAreaTrue.1.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarControllerSnapshotests/gradientLoadingBarController_withIsRelativeToSafeAreaTrue.1.png new file mode 100644 index 0000000..be7cd80 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarControllerSnapshotests/gradientLoadingBarController_withIsRelativeToSafeAreaTrue.1.png differ diff --git a/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/__Snapshots__/GradientLoadingBarViewTestCase/test_gradientLoadingBarView_shouldContainCorrectCustomColors.1.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarViewSnapshotTests/gradientLoadingBarView_withCustomGradientColors.1.png similarity index 96% rename from GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/__Snapshots__/GradientLoadingBarViewTestCase/test_gradientLoadingBarView_shouldContainCorrectCustomColors.1.png rename to Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarViewSnapshotTests/gradientLoadingBarView_withCustomGradientColors.1.png index f6e8ccb..2ff3b1d 100644 Binary files a/GradientLoadingBar/Tests/SnapshotTests/GradientLoadingBarView/__Snapshots__/GradientLoadingBarViewTestCase/test_gradientLoadingBarView_shouldContainCorrectCustomColors.1.png and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarViewSnapshotTests/gradientLoadingBarView_withCustomGradientColors.1.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarViewSnapshotTests/gradientLoadingBarView_withDefaultGradientColors.1.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarViewSnapshotTests/gradientLoadingBarView_withDefaultGradientColors.1.png new file mode 100644 index 0000000..ca95760 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/GradientLoadingBarViewSnapshotTests/gradientLoadingBarView_withDefaultGradientColors.1.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone11.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone11.png new file mode 100644 index 0000000..00ce1d8 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone11.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone11Pro.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone11Pro.png new file mode 100644 index 0000000..bec7f79 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone11Pro.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone11ProMax.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone11ProMax.png new file mode 100644 index 0000000..d019209 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone11ProMax.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12.png new file mode 100644 index 0000000..d46b908 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12Mini.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12Mini.png new file mode 100644 index 0000000..142a866 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12Mini.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12Pro.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12Pro.png new file mode 100644 index 0000000..d46b908 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12Pro.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12ProMax.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12ProMax.png new file mode 100644 index 0000000..3c01e5e Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone12ProMax.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13.png new file mode 100644 index 0000000..0e8ab2b Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13Mini.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13Mini.png new file mode 100644 index 0000000..d2a5b61 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13Mini.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13Pro.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13Pro.png new file mode 100644 index 0000000..0e8ab2b Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13Pro.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13ProMax.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13ProMax.png new file mode 100644 index 0000000..0e8ab2b Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone13ProMax.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone14.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone14.png new file mode 100644 index 0000000..0e8ab2b Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone14.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone14Plus.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone14Plus.png new file mode 100644 index 0000000..0e8ab2b Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withNotchDevice-notchDevice.iPhone14Plus.png differ diff --git a/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withoutNotchDevice.1.png b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withoutNotchDevice.1.png new file mode 100644 index 0000000..b1d18e0 Binary files /dev/null and b/Tests/GradientLoadingBarTests/SnapshotTests/__Snapshots__/NotchGradientLoadingBarControllerSnapshotTests/notchGradientLoadingBarController_withoutNotchDevice.1.png differ diff --git a/Tests/GradientLoadingBarTests/UnitTests/GradientActivityIndicatorView+ViewModelTests.swift b/Tests/GradientLoadingBarTests/UnitTests/GradientActivityIndicatorView+ViewModelTests.swift new file mode 100644 index 0000000..f400161 --- /dev/null +++ b/Tests/GradientLoadingBarTests/UnitTests/GradientActivityIndicatorView+ViewModelTests.swift @@ -0,0 +1,173 @@ +// +// GradientActivityIndicatorView+ViewModelTests.swift +// GradientLoadingBarTests +// +// Created by Felix Mau on 26.08.19. +// Copyright © 2019 Felix Mau. All rights reserved. +// + +import Testing +import UIKit +@testable import GradientLoadingBar + +@MainActor +@Suite +struct `GradientActivityIndicatorView+ViewModelTests` { + // swiftlint:disable:previous type_name + + // MARK: - Types + + typealias ViewModel = GradientActivityIndicatorView.ViewModel + + // MARK: - Test Property `isHidden` + + @Test + func `WHEN initialized THEN isHidden should be false`() { + // When + let viewModel = ViewModel() + + // Then + #expect(!viewModel.isHidden) + } + + // MARK: - Test Property `isAnimating` + + @Test + func `WHEN isHidden is false THEN isAnimating should be true`() { + // Given + let viewModel = ViewModel() + + // When + viewModel.isHidden = false + + // Then + #expect(viewModel.isAnimating) + } + + @Test + func `WHEN isHidden is true THEN isAnimating should be false`() { + // Given + let viewModel = ViewModel() + + // When + viewModel.isHidden = true + + // Then + #expect(!viewModel.isAnimating) + } + + // MARK: - Test Property `bounds` + + @Test + func `WHEN initialized THEN bounds should be zero`() { + // When + let viewModel = ViewModel() + + // Then + #expect(viewModel.bounds == .zero) + } + + // MARK: - Test Property `gradientLayerFrame` + + @Test + func `WHEN setting bounds THEN gradientLayerFrame should be updated with correct width`() { + // Given + let viewModel = ViewModel() + let size = CGSize( + width: .random(in: 1 ... 100_000), + height: .random(in: 1 ... 100_000), + ) + + // When + viewModel.bounds = CGRect(origin: .zero, size: size) + + // Then + let expectedFrame = CGRect( + origin: .zero, + size: CGSize(width: size.width * 3, height: size.height), + ) + + #expect(viewModel.gradientLayerFrame == expectedFrame) + } + + // MARK: - Test Property `animationFromValue` + + @Test + func `WHEN setting bounds THEN animationFromValue should be updated with correct value`() { + // Given + let viewModel = ViewModel() + let size = CGSize( + width: .random(in: 1 ... 100_000), + height: .random(in: 1 ... 100_000), + ) + + // When + viewModel.bounds = CGRect(origin: .zero, size: size) + + // Then + #expect(viewModel.animationFromValue == size.width * -2) + } + + // MARK: - Test Property `animationToValue` + + @Test + func `WHEN initialized THEN animationToValue should be zero`() { + // When + let viewModel = ViewModel() + + // Then + #expect(viewModel.animationToValue == 0) + } + + // MARK: - Test Property `gradientColors` + + @Test + func `WHEN initialized THEN gradientColors should equal default colors`() { + // When + let viewModel = ViewModel() + + // Then + #expect(viewModel.gradientColors == UIColor.GradientLoadingBar.gradientColors) + } + + // MARK: - Test Property `gradientLayerColors` + + @Test + func `WHEN setting gradientColors with a single color THEN gradientLayerColors should equal the single color`() { + // Given + let viewModel = ViewModel() + let gradientColors: [UIColor] = [.red] + + // When + viewModel.gradientColors = gradientColors + + // Then + let expectedGradientLayerColors = [UIColor.red].map(\.cgColor) + #expect(viewModel.gradientLayerColors == expectedGradientLayerColors) + } + + @Test + func `WHEN setting gradientColors THEN gradientLayerColors should equal the infinite gradient colors`() { + // Given + let viewModel = ViewModel() + let gradientColors: [UIColor] = [.red, .yellow, .green] + + // When + viewModel.gradientColors = gradientColors + + // Then + let expectedGradientLayerColors = [UIColor.red, .yellow, .green, .yellow, .red, .yellow, .green].map(\.cgColor) + #expect(viewModel.gradientLayerColors == expectedGradientLayerColors) + } + + // MARK: - Test Property `progressAnimationDuration` + + @Test + func `WHEN initialized THEN progressAnimationDuration should contain default duration`() { + // When + let viewModel = ViewModel() + + // Then + #expect(viewModel.progressAnimationDuration == TimeInterval.GradientLoadingBar.progressDuration) + } +} diff --git a/Tests/GradientLoadingBarTests/UnitTests/GradientLoadingBarController+ViewModelTests.swift b/Tests/GradientLoadingBarTests/UnitTests/GradientLoadingBarController+ViewModelTests.swift new file mode 100644 index 0000000..05f0042 --- /dev/null +++ b/Tests/GradientLoadingBarTests/UnitTests/GradientLoadingBarController+ViewModelTests.swift @@ -0,0 +1,129 @@ +// +// GradientLoadingBarController+ViewModelTests.swift +// GradientLoadingBarTests +// +// Created by Felix Mau on 26.12.17. +// Copyright © 2017 Felix Mau. All rights reserved. +// + +import Combine +import Testing +import UIKit +@testable import GradientLoadingBar + +@MainActor +@Suite +struct `GradientLoadingBarController+ViewModelTests` { + // swiftlint:disable:previous type_name + + // MARK: - Types + + typealias ViewModel = GradientLoadingBarController.ViewModel + + // MARK: - Private Properties + + private let sharedApplicationMock = SharedApplicationMock() + private let notificationCenter = NotificationCenter() + + // MARK: - Test Property `superview` + + @Test + func `WHEN initialized THEN superview should be nil`() { + // When + let viewModel = ViewModel( + sharedApplication: sharedApplicationMock, + notificationCenter: notificationCenter, + ) + + var receivedSuperviews = [UIView?]() + let cancellable = viewModel.superview.sink { + receivedSuperviews.append($0) + } + + // Then + withExtendedLifetime(cancellable) { + #expect(receivedSuperviews == [nil]) + } + } + + @Test + func `WHEN initialized and having a firstKeyWindow THEN superview should equal firstKeyWindow`() { + // Given + let keyWindow = UIWindow() + sharedApplicationMock.firstKeyWindow = keyWindow + + // When + let viewModel = ViewModel( + sharedApplication: sharedApplicationMock, + notificationCenter: notificationCenter, + ) + + // Then + var receivedSuperviews = [UIView?]() + let cancellable = viewModel.superview.sink { + receivedSuperviews.append($0) + } + + // Then + withExtendedLifetime(cancellable) { + #expect(receivedSuperviews == [keyWindow]) + } + } + + @Test + func `WHEN initialized THEN superview should be firstKeyWindow after UIWindow.didBecomeKeyNotification posted`() { + // Given + let viewModel = ViewModel( + sharedApplication: sharedApplicationMock, + notificationCenter: notificationCenter, + ) + + var receivedSuperviews = [UIView?]() + let cancellable = viewModel.superview.sink { + receivedSuperviews.append($0) + } + + // When + let keyWindow = UIWindow() + sharedApplicationMock.firstKeyWindow = keyWindow + notificationCenter.post(name: UIWindow.didBecomeKeyNotification, object: nil) + + // Then + withExtendedLifetime(cancellable) { + let expectedSuperviews = [nil, keyWindow] + #expect(receivedSuperviews == expectedSuperviews) + } + } + + @Test + func `WHEN deinitialized THEN superview should be nil`() { + // Given + let keyWindow = UIWindow() + sharedApplicationMock.firstKeyWindow = keyWindow + + var viewModel: ViewModel? = ViewModel( + sharedApplication: sharedApplicationMock, + notificationCenter: notificationCenter, + ) + + var receivedSuperviews = [UIView?]() + let cancellable = viewModel?.superview.sink { + receivedSuperviews.append($0) + } + + // When + viewModel = nil + + // Then + withExtendedLifetime(cancellable) { + let expectedSuperviews = [keyWindow, nil] + #expect(receivedSuperviews == expectedSuperviews) + } + } +} + +// MARK: - Mocks + +private final class SharedApplicationMock: UIApplicationProtocol { + var firstKeyWindow: UIWindow? +} diff --git a/Tests/GradientLoadingBarTests/UnitTests/GradientLoadingBarView+ViewModelTests.swift b/Tests/GradientLoadingBarTests/UnitTests/GradientLoadingBarView+ViewModelTests.swift new file mode 100644 index 0000000..5459906 --- /dev/null +++ b/Tests/GradientLoadingBarTests/UnitTests/GradientLoadingBarView+ViewModelTests.swift @@ -0,0 +1,103 @@ +// +// GradientLoadingBarView+ViewModelTests.swift +// GradientLoadingBarTests +// +// Created by Felix Mau on 22.03.22. +// Copyright © 2022 Felix Mau. All rights reserved. +// + +import SwiftUI +import Testing +@testable import GradientLoadingBar + +@MainActor +@Suite +struct `GradientLoadingBarView+ViewModelTests` { + // swiftlint:disable:previous type_name + + // MARK: - Types + + typealias ViewModel = GradientLoadingBarView.ViewModel + + // MARK: - Test property `gradientColors` + + @Test + func `WHEN initialized with a single gradient color THEN gradientColors should equal the single color`() { + // Given + let gradientColors: [Color] = [.red] + + // When + let viewModel = ViewModel(gradientColors: gradientColors, progressDuration: 1) + + // Then + let expectedGradientColors: [Color] = [.red] + #expect(viewModel.gradientColors == expectedGradientColors) + } + + @Test + func `WHEN initialized with gradient colors THEN gradientColors should equal the infinite gradient colors`() { + // Given + let gradientColors: [Color] = [.red, .yellow, .green] + + // When + let viewModel = ViewModel(gradientColors: gradientColors, progressDuration: 1) + + // Then + let expectedGradientColors: [Color] = [.red, .yellow, .green, .yellow, .red, .yellow, .green] + #expect(viewModel.gradientColors == expectedGradientColors) + } + + // MARK: - Test Property `gradientOffset` + + @Test + func `WHEN initialized THEN gradientOffset should be zero`() { + // When + let viewModel = ViewModel(gradientColors: [], progressDuration: 1) + + // Then + #expect(viewModel.gradientOffset == 0) + } + + @Test + func `WHEN setting size THEN gradientOffset should be updated with new width`() { + // Given + let viewModel = ViewModel(gradientColors: [], progressDuration: 1) + let size = CGSize( + width: .random(in: 1 ... 100_000), + height: .random(in: 1 ... 100_000), + ) + + // When + viewModel.size = size + + // Then + #expect(viewModel.gradientOffset == size.width) + } + + // MARK: - Test Property `gradientWidth` + + @Test + func `WHEN initialized THEN gradientWidth should be zero`() { + // When + let viewModel = ViewModel(gradientColors: [], progressDuration: 1) + + // Then + #expect(viewModel.gradientWidth == 0) + } + + @Test + func `WHEN setting size THEN gradientWidth should be updated with correct width`() { + // Given + let viewModel = ViewModel(gradientColors: [], progressDuration: 1) + let size = CGSize( + width: .random(in: 1 ... 100_000), + height: .random(in: 1 ... 100_000), + ) + + // When + viewModel.size = size + + // Then + #expect(viewModel.gradientWidth == size.width * 3) + } +} diff --git a/Tests/GradientLoadingBarTests/UnitTests/NotchGradientLoadingBarController+ViewModelTests.swift b/Tests/GradientLoadingBarTests/UnitTests/NotchGradientLoadingBarController+ViewModelTests.swift new file mode 100644 index 0000000..d2bf129 --- /dev/null +++ b/Tests/GradientLoadingBarTests/UnitTests/NotchGradientLoadingBarController+ViewModelTests.swift @@ -0,0 +1,62 @@ +// +// NotchGradientLoadingBarController+ViewModelTests.swift +// GradientLoadingBarTests +// +// Created by Felix Mau on 26.12.17. +// Copyright © 2017 Felix Mau. All rights reserved. +// + +import Testing +@testable import GradientLoadingBar + +@MainActor +@Suite +struct `NotchGradientLoadingBarController+ViewModelTests` { + // swiftlint:disable:previous type_name + + // MARK: - Types + + typealias ViewModel = NotchGradientLoadingBarController.ViewModel + + // MARK: - Tests + + @Test( + arguments: [ + "iPhone12,1": NotchDevice.iPhone11, + "iPhone12,3": .iPhone11Pro, + "iPhone12,5": .iPhone11ProMax, + "iPhone13,1": .iPhone12Mini, + "iPhone13,2": .iPhone12, + "iPhone13,3": .iPhone12Pro, + "iPhone13,4": .iPhone12ProMax, + "iPhone14,4": .iPhone13Mini, + "iPhone14,5": .iPhone13, + "iPhone14,2": .iPhone13Pro, + "iPhone14,3": .iPhone13ProMax, + "iPhone14,7": .iPhone14, + "iPhone14,8": .iPhone14Plus, + ], + ) + func `WHEN initialized with a valid device identifier THEN view model should return correct notch device`( + deviceIdentifier: String, + notchDevice: NotchDevice, + ) { + // When + let viewModel = ViewModel(deviceIdentifier: deviceIdentifier) + + // Then + #expect(viewModel.notchDevice == notchDevice) + } + + @Test + func `WHEN initialized with an invalid device identifier THEN view model should return no notch device`() { + // Given + let deviceIdentifier = "Foo-Bar-đŸ€Ą" + + // When + let viewModel = ViewModel(deviceIdentifier: deviceIdentifier) + + // Then + #expect(viewModel.notchDevice == nil) + } +} diff --git a/_Pods.xcodeproj b/_Pods.xcodeproj deleted file mode 120000 index 3c5a8e7..0000000 --- a/_Pods.xcodeproj +++ /dev/null @@ -1 +0,0 @@ -Example/Pods/Pods.xcodeproj \ No newline at end of file diff --git a/readme.md b/readme.md deleted file mode 100644 index 4e4351d..0000000 --- a/readme.md +++ /dev/null @@ -1,254 +0,0 @@ -GradientLoadingBar -==================== - -![Swift5.0](https://img.shields.io/badge/Swift-5.0-green.svg?style=flat) ![CI Status](https://img.shields.io/github/workflow/status/fxm90/GradientLoadingBar/Continuous%20Integration) ![Code Coverage](https://img.shields.io/codecov/c/github/fxm90/GradientLoadingBar.svg?style=flat) ![Version](https://img.shields.io/cocoapods/v/GradientLoadingBar.svg?style=flat) ![License](https://img.shields.io/cocoapods/l/GradientLoadingBar.svg?style=flat) ![Platform](https://img.shields.io/cocoapods/p/GradientLoadingBar.svg?style=flat) - -A customizable animated gradient loading bar. Inspired by [iOS 7 Progress Bar from Codepen](https://codepen.io/marcobiedermann/pen/LExXWW). - -### Example -![Example][example] - -To run the example project, clone the repo, and open the workspace from the Example directory. - -### Requirements -- Swift 5.5 -- Xcode 13 -- iOS 13.0+ - -**Note:** In case you need support for iOS versions lower than 13, you can fallback to version `2.X.X`. - - -### Integration -##### CocoaPods -[CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate GradientLoadingBar into your Xcode project using CocoaPods, specify it in your `Podfile`: - -```ruby -pod 'GradientLoadingBar', '~> 3.0' -``` - -##### Carthage -[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To integrate GradientLoadingBar into your Xcode project using Carthage, specify it in your `Cartfile`: - -```ogdl -github "fxm90/GradientLoadingBar" ~> 3.0 -``` -Run carthage update to build the framework and drag the built `GradientLoadingBar.framework` into your Xcode project. - - -##### Swift Package Manager -The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but Gradient Loading Bar does support its use on supported platforms. - -Once you have your Swift package set up, adding Gradient Loading Bar as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. - -```swift -dependencies: [ - .package(url: "https://github.com/fxm90/GradientLoadingBar", from: "3.0.0") -] -``` - - -### How to use -This framework provides four classes: - - - **GradientLoadingBar**: A controller, managing the visibility of the `GradientActivityIndicatorView` on the current key window. - - **NotchGradientLoadingBar**: A subclass of `GradientLoadingBar`, wrapping the `GradientActivityIndicatorView` around the notch of the iPhone. - - **GradientActivityIndicatorView**: A `UIView` containing the gradient with the animation. It can be added as a subview to another view either inside the interface builder or programmatically. Both ways are shown inside the example application. - - **GradientLoadingBarView**: A `View` for SwiftUI containing the gradient with the animation. The view can be added to any other SwiftUI view. The example application also contains sample code for this use case. - -#### GradientLoadingBar -To get started, import the module `GradientLoadingBar` into your file and save an instance of `GradientLoadingBar()` on a property of your view-controller. To show the loading bar, simply call the `fadeIn(duration:completion)` method and after your async operations have finished call the `fadeOut(duration:completion)` method. - -```swift -final class UserViewController: UIViewController { - - private let gradientLoadingBar = GradientLoadingBar() - - // ... - - override func viewDidLoad() { - super.viewDidLoad() - - gradientLoadingBar.fadeIn() - - userService.loadUserData { [weak self] _ in - // ... - // Be sure to call this on the main thread! - self?.gradientLoadingBar.fadeOut() - } - } -} -``` - -##### Configuration -You can override the default configuration by calling the initializers with the optional parameters `height` and `isRelativeToSafeArea`: - -```swift -let gradientLoadingBar = GradientLoadingBar( - height: 4.0, - isRelativeToSafeArea: true -) -``` - -###### – Parameter `height: CGFloat` -By setting this parameter you can set the height for the loading bar (defaults to `3.0`) - -###### – Parameter `isRelativeToSafeArea: Bool` -With this parameter you can configure, whether the loading bar should be positioned relative to the safe area (defaults to `true`). - -Example with `isRelativeToSafeArea` set to `true`. -[![Example][basic-example--thumbnail]][basic-example] - - -Example with `isRelativeToSafeArea` set to `false`. -[![Example][safe-area-example--thumbnail]][safe-area-example] - -##### Note -There is a third option which will wrap the loading bar around the iPhone notch. See documentation of the class `NotchGradientLoadingBar` for further details. - -##### Properties -###### – `gradientColors: [UIColor]` -This property adjusts the gradient colors shown on the loading bar. - -###### – `progressAnimationDuration: TimeInterval` -This property adjusts the duration of the animation moving the gradient from left to right. - -##### Methods -###### – `fadeIn(duration:completion)` -This method fades-in the loading bar. You can adjust the duration with corresponding parameter. Furthermore you can pass in a completion handler that gets called once the animation is finished. - -###### – `fadeOut(duration:completion)` -This methods fades-out the loading bar. You can adjust the duration with corresponding parameter. Furthermore you can pass in a completion handler that gets called once the animation is finished. - -##### Custom shared instance (Singleton) -If you need the loading bar on multiple / different parts of your app, you can use the given static `shared` variable: - -```swift -GradientLoadingBar.shared.fadeIn() - -// Do e.g. server calls etc. - -GradientLoadingBar.shared.fadeOut() -``` - -If you wish to customize the shared instance, you can add the following code e.g. to your app delegate `didFinishLaunchingWithOptions` method and overwrite the `shared` variable: - -```swift -GradientLoadingBar.shared = GradientLoadingBar(height: 5.0) -``` - - -#### NotchGradientLoadingBar -This subclass of the `GradientLoadingBar` will wrap the loading bar around the notch of the iPhone. - -For iPhones without a safe area, the behaviour stays the same as mentioned in the above documentation of the `GradientLoadingBar`. - -```swift -let notchGradientLoadingBar = NotchGradientLoadingBar( - height: 3.0 -) -``` - -[![Example][notch-example--thumbnail]][notch-example] - -#### GradientActivityIndicatorView -In case you don't want to add the loading bar onto the key-window, this framework provides the `GradientActivityIndicatorView`, which is a direct subclass of `UIView`. You can add the view to another view either inside the interface builder or programmatically. - -E.g. View added as a subview to a `UINavigationBar`. -[![Example][navigation-bar-example--thumbnail]][navigation-bar-example] - - -E.g. View added as a subview to a `UIButton`. -[![Example][advanced-example--thumbnail]][advanced-example] - -##### Note -The progress-animation starts and stops according to the `isHidden` flag. Setting this flag to `false` will start the animation, setting this to `true` will stop the animation. Often you don't want to directly show / hide the view and instead smoothly fade it in or out. Therefore the view provides the methods `fadeIn(duration:completion)` and `fadeOut(duration:completion)`. Based on the gist [`UIView+AnimateAlpha.swift`](https://gist.github.com/fxm90/723b5def31b46035cd92a641e3b184f6), these methods adjust the `alpha` value of the view and update the `isHidden` flag accordingly. - -##### Properties -###### – `gradientColors: [UIColor]` -This property adjusts the gradient colors shown on the loading bar. - -###### – `progressAnimationDuration: TimeInterval` -This property adjusts the duration of the animation moving the gradient from left to right. - -*To see all these screenshots in a real app, please have a look at the **example application**. For further customization you can also subclass `GradientLoadingBar` and overwrite the method `setupConstraints()`.* - - -#### GradientLoadingBarView -This is the SwiftUI variant for the `GradientActivityIndicatorView`. The view can be configured via the two parameters `gradientColors: [Color]` and `progressDuration: TimeInterval` passed to the initializer. - -##### – `gradientColors: [Color]` : -This parameter adjusts the gradient colors shown on the loading bar. - -##### – `progressDuration: TimeInterval` : -This parameter adjusts the duration of the animation moving the gradient from left to right. - -The visibility of the view can be updated with the view modifier [`opacity()`](https://developer.apple.com/documentation/swiftui/view/opacity(_:)) or [`hidden()`](https://developer.apple.com/documentation/swiftui/view/hidden()). - -To animate the visibility changes you need to create a property with the `@State` property wrapper, and update the value from a [`withAnimation`](https://developer.apple.com/documentation/swiftui/withanimation(_:_:)) block, e.g. - -```swift -struct ExampleView: some View { - - @State - private var isVisible = false - - var body: some View { - VStack { - GradientLoadingBarView() - .frame(maxWidth: .infinity, maxHeight: 3) - .cornerRadius(1.5) - .opacity(isVisible ? 1 : 0) - - Button("Toggle visibility") { - withAnimation(.easeInOut) { - isVisible.toggle() - } - } - } - } -} -``` - - -### Troubleshooting -#### Interface Builder Support -Unfortunately the Interface Builder support is currently broken for Cocoapods frameworks. If you need Interface Builder support, add the following code to your Podfile and run `pod install` again. Afterwards you should be able to use the `GradientLoadingBar` inside the Interface Builder :) - -``` - post_install do |installer| - installer.pods_project.build_configurations.each do |config| - next unless config.name == 'Debug' - - config.build_settings['LD_RUNPATH_SEARCH_PATHS'] = [ - '$(FRAMEWORK_SEARCH_PATHS)' - ] - end - end - ``` -Source: [Cocoapods – Issue 7606](https://github.com/CocoaPods/CocoaPods/issues/7606#issuecomment-484294739) - - -### Author -Felix Mau (me(@)felix.hamburg) - -### License - -GradientLoadingBar is available under the MIT license. See the LICENSE file for more info. - -[example]: Assets/screen.gif - -[basic-example]: Assets/basic-example.png -[basic-example--thumbnail]: Assets/basic-example--thumbnail.png - -[safe-area-example]: Assets/safe-area-example.png -[safe-area-example--thumbnail]: Assets/safe-area-example--thumbnail.png - -[notch-example]: Assets/notch-example.png -[notch-example--thumbnail]: Assets/notch-example--thumbnail.png - -[navigation-bar-example]: Assets/navigation-bar-example.png -[navigation-bar-example--thumbnail]: Assets/navigation-bar-example--thumbnail.png - - -[advanced-example]: Assets/advanced-example.png -[advanced-example--thumbnail]: Assets/advanced-example--thumbnail.png