Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87d8a1deda | ||
|
|
bd7828f8ee | ||
|
|
d8728a8fe8 | ||
|
|
8528c2b71c | ||
|
|
7baa3b116c | ||
|
|
1cab02e3d3 | ||
|
|
81a830c36f | ||
|
|
27392ece2a | ||
|
|
4455b1125e | ||
|
|
dd1323e31c | ||
|
|
ae3a7fa25e | ||
|
|
85cd3fe00d | ||
|
|
8ad9d7051a | ||
|
|
27dfd49656 | ||
|
|
709980d6a7 | ||
|
|
cd941efb52 | ||
|
|
ebc76148ce | ||
|
|
730a4cbe42 | ||
|
|
3d4a8ec568 | ||
|
|
231330730a | ||
|
|
b6593371cd | ||
|
|
8757179241 | ||
|
|
55372e4bf5 | ||
|
|
64ec43e8f8 | ||
|
|
a878ba4ff2 | ||
|
|
df453eeb5a | ||
|
|
0cfdc7d269 | ||
|
|
8ca35493fc | ||
|
|
08ac0a5224 | ||
|
|
385d133dfc | ||
|
|
901fadaffb | ||
|
|
097b26b5ad | ||
|
|
066064280b | ||
|
|
c6a9fdb124 | ||
|
|
8090a3502a | ||
|
|
9e7d80e3d2 | ||
|
|
c6ed57a24e | ||
|
|
999a3497de | ||
|
|
4b2987dc79 | ||
|
|
b209ecae78 | ||
|
|
e58c15c381 | ||
|
|
345a874a04 | ||
|
|
47166ac314 | ||
|
|
707793be13 | ||
|
|
2b19e431ec | ||
|
|
4521bc8622 | ||
|
|
77b7a5929e | ||
|
|
3c145963e9 | ||
|
|
fcb33e68f7 | ||
|
|
e82081460a | ||
|
|
4de6fd74ec | ||
|
|
49cf8c9ba2 | ||
|
|
5aa2f5d5ba | ||
|
|
9779449ab4 | ||
|
|
60ccf4b1d2 | ||
|
|
ee6c985326 | ||
|
|
f9b72b1621 | ||
|
|
84e1d0f2a4 | ||
|
|
85fa8e3851 | ||
|
|
0ec26dfee9 | ||
|
|
d979f65b80 | ||
|
|
01c7df73f0 | ||
|
|
a7c82024d0 | ||
|
|
0b6424f4a8 | ||
|
|
bfcdca3efb | ||
|
|
3c82b1464c | ||
|
|
ea234f73bb | ||
|
|
0bfd932196 | ||
|
|
fa7e30c44f | ||
|
|
38115a2827 | ||
|
|
596692c2d4 | ||
|
|
68c39399c4 | ||
|
|
8a85860ba1 | ||
|
|
24b380bf50 | ||
|
|
c4294823e7 | ||
|
|
c319b42a49 | ||
|
|
2addd9fa7d | ||
|
|
8fb9963150 | ||
|
|
68a53add0c | ||
|
|
b786421871 | ||
|
|
4e52985153 | ||
|
|
ae24582345 | ||
|
|
61d787e1d6 | ||
|
|
5c45b2e9f5 | ||
|
|
ff7c892079 | ||
|
|
5f48a38dca | ||
|
|
104d4c8f60 | ||
|
|
0e0b31ffbe | ||
|
|
244e1eba94 | ||
|
|
4e5aaae570 | ||
|
|
dfc9049be2 | ||
|
|
c0f27a3e48 | ||
|
|
54a413d986 | ||
|
|
3d24237307 | ||
|
|
7c02e472d7 | ||
|
|
00275d6778 | ||
|
|
1b41081034 | ||
|
|
f6a959a4b5 | ||
|
|
ffdc50eada | ||
|
|
2ae619db5a | ||
|
|
587e6d0095 | ||
|
|
9012d6ec6a | ||
|
|
8730c614de | ||
|
|
f7486690ac | ||
|
|
846005af52 | ||
|
|
cd14d3cfee | ||
|
|
9ae07a9601 | ||
|
|
f0e4a7ec26 | ||
|
|
3d8e5c32fc | ||
|
|
240150d8c8 | ||
|
|
6a80af2cd7 | ||
|
|
b981df3519 | ||
|
|
6c9c0ab1e1 | ||
|
|
563e8c6ecf | ||
|
|
6980c0e3c3 | ||
|
|
94106389e2 | ||
|
|
b2279955a2 | ||
|
|
0b99b7853b | ||
|
|
bc6575e9db | ||
|
|
7b586da85b | ||
|
|
66467cf4d9 | ||
|
|
8e7650dd46 | ||
|
|
8b257545ee | ||
|
|
285d8ef2d4 | ||
|
|
090a59959f | ||
|
|
67f356caf0 | ||
|
|
85a61b4433 | ||
|
|
f44ab07301 | ||
|
|
ae1ff09572 | ||
|
|
e654161db3 | ||
|
|
df93f889b5 | ||
|
|
63f66740c9 | ||
|
|
80e113d194 | ||
|
|
91fdbbfeb6 | ||
|
|
555b5309a9 | ||
|
|
cafd394cb7 | ||
|
|
e558c54cb7 | ||
|
|
9382248471 | ||
|
|
44307a6b7a | ||
|
|
f54023a6b9 | ||
|
|
67f6ab75cc | ||
|
|
1f00f1d61f | ||
|
|
41c084b27d | ||
|
|
e8d03a9ccc | ||
|
|
ecad3b98b9 | ||
|
|
d97b99ab83 | ||
|
|
0e20b0fb0e | ||
|
|
3e1c18818a | ||
|
|
76cd8c3ddf | ||
|
|
66eace181a | ||
|
|
bbe2ddfe2f | ||
|
|
0783d958a2 | ||
|
|
07f2733ef8 | ||
|
|
03b9fac539 | ||
|
|
be40ac8c35 | ||
|
|
dec430d951 | ||
|
|
25de79f73d | ||
|
|
9ca33c4432 | ||
|
|
f4b85195f5 | ||
|
|
7309d0bd21 | ||
|
|
d9c7840323 | ||
|
|
d484da4f83 |
@@ -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
|
|
||||||
@@ -2,63 +2,61 @@
|
|||||||
# continuous-integration.yml
|
# continuous-integration.yml
|
||||||
# Created by Felix Mau (https://felix.hamburg)
|
# 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
|
name: Continuous Integration
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
pull_request:
|
pull_request:
|
||||||
schedule:
|
push:
|
||||||
# Run workflow every day at midnight.
|
branches:
|
||||||
#
|
- main
|
||||||
# - Note: "Scheduled workflows run on the latest commit on the default or base branch."
|
workflow_dispatch:
|
||||||
# https://docs.github.com/en/actions/reference/events-that-trigger-workflows#scheduled-events
|
|
||||||
- cron: '0 0 * * *'
|
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel.
|
|
||||||
jobs:
|
jobs:
|
||||||
# This workflow starts with a job called "build-and-test".
|
# Lint and Format
|
||||||
build-and-test:
|
# Ensures that the code adheres to the defined style guidelines.
|
||||||
name: Build & Test
|
lint-and-format:
|
||||||
runs-on: macos-latest
|
runs-on: macos-26
|
||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job.
|
|
||||||
steps:
|
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
|
- name: Check out repository code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
|
- name: Install SwiftFormat via Homebrew
|
||||||
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
|
run: brew install swiftformat
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install SwiftLint via Homebrew
|
||||||
working-directory: ./Example
|
run: brew install swiftlint
|
||||||
run: bundle install
|
|
||||||
|
|
||||||
- name: Execute SwiftFormat and treat any formatting errors as real errors.
|
- name: SwiftFormat
|
||||||
working-directory: ./Example
|
run: make format-check
|
||||||
run: bundle exec fastlane format
|
|
||||||
|
|
||||||
- name: Execute SwiftLint and treat any formatting errors as real errors.
|
- name: SwiftLint
|
||||||
working-directory: ./Example
|
run: make lint
|
||||||
run: bundle exec fastlane lint
|
|
||||||
|
|
||||||
- name: Execute tests.
|
# Tests
|
||||||
working-directory: ./Example
|
# Ensures that the package compiles and that all tests pass, including code coverage reporting to Codecov.
|
||||||
run: bundle exec fastlane tests
|
tests:
|
||||||
|
runs-on: macos-26
|
||||||
|
needs: lint-and-format
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Execute validation of library.
|
- name: Execute tests
|
||||||
working-directory: ./Example
|
run: make test
|
||||||
run: bundle exec fastlane pod_lint
|
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v1.2.1
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
fail_ci_if_error: true
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
# 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
|
||||||
|
|
||||||
|
- name: Build Example Application
|
||||||
|
run: make build-example-application
|
||||||
|
|||||||
@@ -1,51 +1,8 @@
|
|||||||
# OS X
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/.build
|
||||||
# Xcode
|
/Packages
|
||||||
build/
|
|
||||||
.build/
|
|
||||||
*.pbxuser
|
|
||||||
!default.pbxuser
|
|
||||||
*.mode1v3
|
|
||||||
!default.mode1v3
|
|
||||||
*.mode2v3
|
|
||||||
!default.mode2v3
|
|
||||||
*.perspectivev3
|
|
||||||
!default.perspectivev3
|
|
||||||
xcuserdata/
|
xcuserdata/
|
||||||
*.xccheckout
|
DerivedData/
|
||||||
profile
|
.swiftpm/configuration/registries.json
|
||||||
*.moved-aside
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
DerivedData
|
.netrc
|
||||||
*.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
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
5.1
|
6.2
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 585 KiB |
|
After Width: | Height: | Size: 428 KiB |
@@ -0,0 +1,125 @@
|
|||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1040" height="600" xml:space="preserve" id="cutout">
|
||||||
|
<!-- Generated by PaintCode - http://www.paintcodeapp.com -->
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="cutout-gradient-bezier18" x1="207.78" y1="187.14" x2="912.47" y2="590.37" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.14" stop-color="rgb(0, 0, 0)" stop-opacity="0"></stop>
|
||||||
|
<stop offset="0.44" stop-color="rgb(64, 82, 91)" stop-opacity="0.05"></stop>
|
||||||
|
<stop offset="1" stop-color="rgb(129, 163, 182)" stop-opacity="0.09"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="cutout-gradient2-rectangle" x1="559" y1="564" x2="559" y2="382" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="rgb(255, 255, 255)" stop-opacity="1"></stop>
|
||||||
|
<stop offset="1" stop-color="rgb(255, 255, 255)" stop-opacity="0"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path id="cutout-bezier26" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 928.5,563.5 L 928.5,259"></path>
|
||||||
|
<path id="cutout-bezier25" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 178.5,562.5 L 178.5,259"></path>
|
||||||
|
<path id="cutout-bezier18" stroke="none" fill="url(#cutout-gradient-bezier18)" d="M 929,561.48 L 929,306.52 C 929,267.79 929,248.42 922.41,227.57 914.12,204.81 896.19,186.88 873.42,178.6 855.96,172.83 833.21,172 794.48,172 L 781.34,172 C 776.06,172 773.42,172 770.58,172.9 767.47,174.03 765.03,176.47 763.9,179.58 763.11,181.96 763,185.06 763,190.34 L 763,192 C 763,214.09 745.09,232 723,232 L 385,232 C 362.91,232 345,214.09 345,192 L 345,190.34 C 345,185.06 345,182.42 344.1,179.58 342.97,176.47 340.53,174.03 337.42,172.9 335.04,172.11 331.94,172 326.66,172 L 313.52,172 C 274.79,172 267.5,172 267.5,172 218.9,172 179.5,211.4 179.5,260 179.5,260 179,267.79 179,306.52 L 179,561.48"></path>
|
||||||
|
<rect id="cutout-rectangle" stroke="none" fill="url(#cutout-gradient2-rectangle)" x="38" y="382" width="1042" height="182"></rect>
|
||||||
|
<circle id="cutout-oval3" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="none" cx="383.5" cy="192.5" r="40"></circle>
|
||||||
|
<path id="cutout-bezier2" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 266.5,467.5 L 266.5,267"></path>
|
||||||
|
<circle id="cutout-oval4" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="none" cx="0" cy="-0" r="12" transform="translate(331.5, 184.5)"></circle>
|
||||||
|
<circle id="cutout-oval" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="none" cx="266.5" cy="260.5" r="88"></circle>
|
||||||
|
<g id="cutout-group8">
|
||||||
|
<path id="cutout-bezier13" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-linecap="square" stroke-miterlimit="10" fill="none" d="M 178.5,58.5 L 928.5,58.5"></path>
|
||||||
|
<polygon id="cutout-polygon" stroke="none" fill="rgb(65, 153, 231)" points="0,-9 3.68,4.5 -3.68,4.5" transform="translate(920, 58.5) rotate(90)"></polygon>
|
||||||
|
<polygon id="cutout-polygon2" stroke="none" fill="rgb(65, 153, 231)" points="-0,-9 -3.68,4.5 3.68,4.5" transform="translate(187, 58.5) rotate(-90)"></polygon>
|
||||||
|
|
||||||
|
<text fill="rgb(65, 153, 231)" font-family="AvenirNext-Medium, 'Avenir Next', sans-serif" font-size="18" x="526.52" y="16" text-anchor="middle"><tspan x="553.5" y="45">375 pt</tspan></text>
|
||||||
|
</g>
|
||||||
|
<g id="cutout-group9">
|
||||||
|
<path id="cutout-bezier14" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-linecap="square" stroke-miterlimit="10" fill="none" d="M 178.5,125.5 L 343.5,125.5"></path>
|
||||||
|
<polygon id="cutout-polygon3" stroke="none" fill="rgb(65, 153, 231)" points="0,-9 3.68,4.5 -3.68,4.5" transform="translate(335, 125.5) rotate(90)"></polygon>
|
||||||
|
<polygon id="cutout-polygon4" stroke="none" fill="rgb(65, 153, 231)" points="-0,-9 -3.68,4.5 3.68,4.5" transform="translate(187, 125.5) rotate(-90)"></polygon>
|
||||||
|
|
||||||
|
<text fill="rgb(65, 153, 231)" font-family="AvenirNext-Medium, 'Avenir Next', sans-serif" font-size="18" x="239.37" y="83" text-anchor="middle"><tspan x="261" y="112">83 pt</tspan></text>
|
||||||
|
</g>
|
||||||
|
<g id="cutout-group10">
|
||||||
|
<path id="cutout-bezier32" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-linecap="square" stroke-miterlimit="10" fill="none" d="M 762.5,125.5 L 928.5,125.5"></path>
|
||||||
|
<polygon id="cutout-polygon5" stroke="none" fill="rgb(65, 153, 231)" points="0,-9 3.68,4.5 -3.68,4.5" transform="translate(920, 125.5) rotate(90)"></polygon>
|
||||||
|
<polygon id="cutout-polygon6" stroke="none" fill="rgb(65, 153, 231)" points="-0,-9 -3.68,4.5 3.68,4.5" transform="translate(771, 125.5) rotate(-90)"></polygon>
|
||||||
|
|
||||||
|
<text fill="rgb(65, 153, 231)" font-family="AvenirNext-Medium, 'Avenir Next', sans-serif" font-size="18" x="823.87" y="83" text-anchor="middle"><tspan x="845.5" y="112">83 pt</tspan></text>
|
||||||
|
</g>
|
||||||
|
<g id="cutout-group11">
|
||||||
|
<path id="cutout-bezier33" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-linecap="square" stroke-miterlimit="10" fill="none" d="M 343.5,125.5 L 762.5,125.5"></path>
|
||||||
|
<polygon id="cutout-polygon7" stroke="none" fill="rgb(65, 153, 231)" points="0,-9 3.68,4.5 -3.68,4.5" transform="translate(754, 125.5) rotate(90)"></polygon>
|
||||||
|
<polygon id="cutout-polygon8" stroke="none" fill="rgb(65, 153, 231)" points="-0,-9 -3.68,4.5 3.68,4.5" transform="translate(352, 125.5) rotate(-90)"></polygon>
|
||||||
|
|
||||||
|
<text fill="rgb(65, 153, 231)" font-family="AvenirNext-Medium, 'Avenir Next', sans-serif" font-size="18" x="526.02" y="83" text-anchor="middle"><tspan x="553" y="112">209 pt</tspan></text>
|
||||||
|
</g>
|
||||||
|
<g id="cutout-group12">
|
||||||
|
<path id="cutout-bezier34" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-linecap="square" stroke-miterlimit="10" fill="none" d="M 94.5,221.5 L 94.5,186.5"></path>
|
||||||
|
<polygon id="cutout-polygon9" stroke="none" fill="rgb(65, 153, 231)" points="0,-9 3.68,4.5 -3.68,4.5" transform="translate(94.5, 223.5) rotate(180)"></polygon>
|
||||||
|
<polygon id="cutout-polygon10" stroke="none" fill="rgb(65, 153, 231)" points="-0,-9 -3.68,4.5 3.68,4.5" transform="translate(94.5, 182.5)"></polygon>
|
||||||
|
|
||||||
|
<text fill="rgb(65, 153, 231)" font-family="AvenirNext-Medium, 'Avenir Next', sans-serif" font-size="18" x="43.75" y="188" text-anchor="end"><tspan x="87" y="208">30 pt</tspan></text>
|
||||||
|
</g>
|
||||||
|
<path id="cutout-bezier5" opacity="0.3" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 82,232.5 L 717.5,232.5"></path>
|
||||||
|
<path id="cutout-bezier7" opacity="0.3" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 343.5,192 L 343.5,108"></path>
|
||||||
|
<path id="cutout-bezier4" opacity="0.3" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 82,172.5 L 840.5,172.5"></path>
|
||||||
|
<path id="cutout-bezier8" opacity="0.3" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 762.5,192 L 762.5,108"></path>
|
||||||
|
<path id="cutout-bezier9" opacity="0.3" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 928.5,373 L 928.5,38"></path>
|
||||||
|
<path id="cutout-bezier10" opacity="0.3" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 178.5,259.5 L 178.5,39"></path>
|
||||||
|
<circle id="cutout-oval2" opacity="0.2" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="none" cx="840.5" cy="260.5" r="88"></circle>
|
||||||
|
<circle id="cutout-oval5" opacity="0.2" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="none" cx="722.5" cy="192.5" r="40"></circle>
|
||||||
|
<circle id="cutout-oval6" opacity="0.2" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="none" cx="0" cy="-0" r="12" transform="translate(774.5, 184.5)"></circle>
|
||||||
|
<circle id="cutout-oval10" stroke="none" fill="rgb(65, 153, 231)" cx="266.5" cy="470.5" r="3.5"></circle>
|
||||||
|
<g id="cutout-group">
|
||||||
|
<path id="cutout-bezier11" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 331.5,389.5 L 331.5,191"></path>
|
||||||
|
<circle id="cutout-oval12" stroke="none" fill="rgb(65, 153, 231)" cx="331.5" cy="389.5" r="3.5"></circle>
|
||||||
|
</g>
|
||||||
|
<path id="cutout-bezier17" stroke="rgb(65, 153, 231)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 383.5,324.5 L 383.5,202"></path>
|
||||||
|
<circle id="cutout-oval13" stroke="none" fill="rgb(65, 153, 231)" cx="383.5" cy="325.5" r="3.5"></circle>
|
||||||
|
<g id="cutout-group2">
|
||||||
|
<path id="cutout-bezier15" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 383.5,202 L 383.5,189"></path>
|
||||||
|
<path id="cutout-bezier16" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 377,195.5 L 390,195.5"></path>
|
||||||
|
<circle id="cutout-oval8" stroke="none" fill="rgb(0, 0, 0)" cx="383.5" cy="195.5" r="3.5"></circle>
|
||||||
|
</g>
|
||||||
|
<g id="cutout-group3" opacity="0.3">
|
||||||
|
<circle id="cutout-oval9" stroke="none" fill="rgb(0, 0, 0)" cx="721.5" cy="193.5" r="3.5"></circle>
|
||||||
|
<path id="cutout-bezier19" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 721.5,200 L 721.5,187"></path>
|
||||||
|
<path id="cutout-bezier20" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 715,193.5 L 728,193.5"></path>
|
||||||
|
</g>
|
||||||
|
<g id="cutout-group4" opacity="0.2">
|
||||||
|
<circle id="cutout-oval14" stroke="none" fill="rgb(0, 0, 0)" cx="839.5" cy="260.5" r="3.5"></circle>
|
||||||
|
<path id="cutout-bezier21" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 839.5,267 L 839.5,254"></path>
|
||||||
|
<path id="cutout-bezier22" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 833,260.5 L 846,260.5"></path>
|
||||||
|
</g>
|
||||||
|
<g id="cutout-group5" opacity="0.2">
|
||||||
|
<circle id="cutout-oval15" stroke="none" fill="rgb(0, 0, 0)" cx="774.5" cy="184.5" r="3.5"></circle>
|
||||||
|
<path id="cutout-bezier23" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 774.5,191 L 774.5,178"></path>
|
||||||
|
<path id="cutout-bezier24" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 768,184.5 L 781,184.5"></path>
|
||||||
|
</g>
|
||||||
|
<path id="cutout-bezier27" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 723,232.5 L 383,232.5"></path>
|
||||||
|
<path id="cutout-bezier28" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 332,172.5 L 265,172.5"></path>
|
||||||
|
<path id="cutout-bezier29" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 841.5,172.5 L 774,172.5"></path>
|
||||||
|
|
||||||
|
<text fill="rgb(65, 153, 231)" font-family="AvenirNext-Medium, 'Avenir Next', sans-serif" font-size="18" x="281" y="454"><tspan x="281" y="476">40 pt radius</tspan></text>
|
||||||
|
|
||||||
|
<text fill="rgb(65, 153, 231)" font-family="AvenirNext-Medium, 'Avenir Next', sans-serif" font-size="18" x="346" y="373"><tspan x="346" y="395">6 pt radius</tspan></text>
|
||||||
|
|
||||||
|
<text fill="rgb(65, 153, 231)" font-family="AvenirNext-Medium, 'Avenir Next', sans-serif" font-size="18" x="403" y="309"><tspan x="403" y="331">20 pt radius</tspan></text>
|
||||||
|
<path id="cutout-oval16" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="none" d="M 383.5,232.5 C 361.41,232.5 343.5,214.59 343.5,192.5"></path>
|
||||||
|
<path id="cutout-oval17" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="none" d="M 178.5,260.5 C 178.5,211.9 217.9,172.5 266.5,172.5"></path>
|
||||||
|
<path id="cutout-oval18" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="none" d="M 331.5,172.5 C 338.13,172.5 343.5,177.87 343.5,184.5"></path>
|
||||||
|
<path id="cutout-bezier30" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 343.5,184.5 L 343.5,194.5"></path>
|
||||||
|
<path id="cutout-oval19" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="none" d="M 774.5,172.5 C 767.87,172.5 762.5,177.87 762.5,184.5"></path>
|
||||||
|
<path id="cutout-oval20" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="none" d="M 722.5,232.5 C 744.59,232.5 762.5,214.59 762.5,192.5"></path>
|
||||||
|
<path id="cutout-bezier31" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 762.5,184 L 762.5,194.5"></path>
|
||||||
|
<path id="cutout-oval21" stroke="rgb(0, 0, 0)" stroke-width="3" stroke-miterlimit="10" fill="none" d="M 928.5,260.5 C 928.5,211.9 889.1,172.5 840.5,172.5"></path>
|
||||||
|
<g id="cutout-group6">
|
||||||
|
<path id="cutout-bezier" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 266.5,267 L 266.5,254"></path>
|
||||||
|
<path id="cutout-bezier12" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 260,260.5 L 273,260.5"></path>
|
||||||
|
<circle id="cutout-oval7" stroke="none" fill="rgb(0, 0, 0)" cx="266.5" cy="260.5" r="3.5"></circle>
|
||||||
|
</g>
|
||||||
|
<g id="cutout-group7">
|
||||||
|
<path id="cutout-bezier3" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 331.5,191 L 331.5,178"></path>
|
||||||
|
<path id="cutout-bezier6" stroke="rgb(0, 0, 0)" stroke-width="1" stroke-miterlimit="10" fill="rgb(255, 255, 255)" d="M 325,184.5 L 338,184.5"></path>
|
||||||
|
<circle id="cutout-oval11" stroke="none" fill="rgb(0, 0, 0)" cx="331.5" cy="184.5" r="3.5"></circle>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Manually added copyright -->
|
||||||
|
<text fill="rgb(0, 0, 0)" font-family="AvenirNext-Medium, 'Avenir Next', sans-serif" font-size="15" x="560" y="560" text-anchor="middle">
|
||||||
|
Source: https://www.paintcodeapp.com/news/iphone-x-screen-demystified
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 162 KiB |
|
After Width: | Height: | Size: 385 KiB |
|
After Width: | Height: | Size: 329 KiB |
|
After Width: | Height: | Size: 365 KiB |
|
After Width: | Height: | Size: 331 KiB |
|
After Width: | Height: | Size: 593 KiB |
|
After Width: | Height: | Size: 429 KiB |
|
Before Width: | Height: | Size: 169 KiB |
|
Before Width: | Height: | Size: 155 KiB |
@@ -1,4 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
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).
|
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,160 +8,307 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [2.2.3] - 2021-29-12
|
## [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 iOS `13.0`. Otherwise there are no breaking changes.
|
||||||
|
|
||||||
|
## [2.3.5] - 2022-12-09
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed incorrect layout on `NotchGradientLoadingBar` when using an increased height ([#026], thanks to [alinfarcas12](https://github.com/alinfarcas12))
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## [2.3.3] - 2022-08-09
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- 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])
|
||||||
|
|
||||||
|
[#029]: https://github.com/fxm90/GradientLoadingBar/issues/29
|
||||||
|
|
||||||
|
## [2.3.0] - 2022-06-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add `GradientLoadingBarView` to support SwiftUI
|
||||||
|
|
||||||
|
## [2.2.5] - 2022-27-03
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- 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"
|
||||||
|
|
||||||
|
## [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))
|
||||||
|
|
||||||
[#026]: https://github.com/fxm90/GradientLoadingBar/issues/26
|
[#026]: https://github.com/fxm90/GradientLoadingBar/issues/26
|
||||||
|
|
||||||
## [2.2.2] - 2021-05-10
|
## [2.2.2] - 2021-05-10
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed missing `return` ([#025])
|
|
||||||
|
- Fixed missing `return` ([#025])
|
||||||
|
|
||||||
[#025]: https://github.com/fxm90/GradientLoadingBar/issues/25
|
[#025]: https://github.com/fxm90/GradientLoadingBar/issues/25
|
||||||
|
|
||||||
## [2.2.1] - 2021-05-10
|
## [2.2.1] - 2021-05-10
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed invalid import ([#024])
|
|
||||||
|
- Fixed invalid import ([#024])
|
||||||
|
|
||||||
[#024]: https://github.com/fxm90/GradientLoadingBar/issues/24
|
[#024]: https://github.com/fxm90/GradientLoadingBar/issues/24
|
||||||
|
|
||||||
## [2.2.0] - 2021-04-10
|
## [2.2.0] - 2021-04-10
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Added support for iPhone 13 ([#023])
|
|
||||||
|
- Added support for iPhone 13 ([#023])
|
||||||
|
|
||||||
[#023]: https://github.com/fxm90/GradientLoadingBar/issues/23
|
[#023]: https://github.com/fxm90/GradientLoadingBar/issues/23
|
||||||
|
|
||||||
## [2.1.1] - 2021-12-05
|
## [2.1.1] - 2021-12-05
|
||||||
|
|
||||||
### Fixed
|
### 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
|
[#020]: https://github.com/fxm90/GradientLoadingBar/issues/20
|
||||||
|
|
||||||
## [2.1.0] - 2020-17-06
|
## [2.1.0] - 2020-17-06
|
||||||
|
|
||||||
### Added
|
### 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
|
## [2.0.3] - 2020-18-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Added support for Swift Package Manager.
|
|
||||||
|
- Added support for Swift Package Manager.
|
||||||
|
|
||||||
## [2.0.2] - 2019-20-12
|
## [2.0.2] - 2019-20-12
|
||||||
|
|
||||||
### Changed
|
### 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
|
## [2.0.1] - 2019-12-10
|
||||||
|
|
||||||
### Changed
|
### 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
|
## [2.0.0] - 2019-02-09
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Splitted framework into two classes:
|
|
||||||
- **GradientLoadingBar**: A controller, managing the visibility of the `GradientActivityIndicatorView` on the current key window.
|
- Split framework into two classes:
|
||||||
- **GradientActivityIndicatorView**: A `UIView` containing the gradient with the animation, with support for Interface Builder.
|
- **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
|
## [1.1.17] - 2019-30-06
|
||||||
|
|
||||||
### Changed
|
### 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
|
## [1.1.16] - 2019-08-04
|
||||||
|
|
||||||
### Changed
|
### 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 by [Emil Bellmann](https://github.com/emilrb) in Pull Request [#13](https://github.com/fxm90/GradientLoadingBar/pull/13).
|
||||||
- Added support for Swift 5.0
|
- Added support for Swift 5.0
|
||||||
|
|
||||||
## [1.1.15] - 2019-14-02
|
## [1.1.15] - 2019-14-02
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Remove dependency `Observable` in favour of a more lightweight implementation
|
- Remove dependency `Observable` in favour of a more lightweight implementation
|
||||||
- Small internal refactorings and cleanup
|
- Small internal refactorings and cleanup
|
||||||
|
|
||||||
## [1.1.14] - 2018-20-12
|
## [1.1.14] - 2018-20-12
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Fix fade-out animation not working in iOS 12 (https://openradar.appspot.com/46753872)
|
|
||||||
|
- Fixed fade-out animation not working in iOS 12 (https://openradar.appspot.com/46753872)
|
||||||
|
|
||||||
## [1.1.13] - 2018-14-11
|
## [1.1.13] - 2018-14-11
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Adapt code to support new version from dependency `Observable`
|
- Adapt code to support new version from dependency `Observable`
|
||||||
|
|
||||||
## [1.1.12] - 2018-22-09
|
## [1.1.12] - 2018-22-09
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Updates for Swift 4.2
|
- Updates for Swift 4.2
|
||||||
- Refactored to observables
|
- 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)
|
- 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
|
## [1.1.11] - 2018-04-06
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Carthage Support
|
|
||||||
|
- Carthage Support
|
||||||
|
|
||||||
## [1.1.10] - 2018-14-04
|
## [1.1.10] - 2018-14-04
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Updates for Swift 4.1
|
|
||||||
- Formatted code
|
- Updates for Swift 4.1
|
||||||
|
- Formatted code
|
||||||
|
|
||||||
## [1.1.9] - 2018-27-01
|
## [1.1.9] - 2018-27-01
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Formatted code
|
|
||||||
- Refactorings
|
- Formatted code
|
||||||
|
- Refactorings
|
||||||
|
|
||||||
## [1.1.8] - 2018-20-01
|
## [1.1.8] - 2018-20-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Further documentation
|
|
||||||
- Further examples
|
- Further documentation
|
||||||
|
- Further examples
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Refactorings (moved all logic into view model)
|
|
||||||
|
- Refactorings (moved all logic into view model)
|
||||||
|
|
||||||
## [1.1.7] - 2017-12-30
|
## [1.1.7] - 2017-12-30
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Adapt layout for iPhoneX
|
|
||||||
- Convert code to Swift 4
|
- Adapt layout for iPhoneX
|
||||||
- Refactor code to MVVM
|
- Convert code to Swift 4
|
||||||
|
- Refactor code to MVVM
|
||||||
|
|
||||||
## [1.1.6] - 2017-10-30
|
## [1.1.6] - 2017-10-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Allow setting a custom superview
|
|
||||||
- Documentation
|
- Allow setting a custom superview
|
||||||
|
- Documentation
|
||||||
|
|
||||||
## [1.1.5] - 2017-10-03
|
## [1.1.5] - 2017-10-03
|
||||||
|
|
||||||
### Fixed
|
### 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
|
[#001]: https://github.com/fxm90/GradientLoadingBar/issues/1
|
||||||
|
|
||||||
## [1.1.4] - 2017-10-01
|
## [1.1.4] - 2017-10-01
|
||||||
|
|
||||||
### Added
|
### 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
|
## [1.1.3] - 2017-09-25
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix 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
|
## [1.1.2] - 2017-08-27
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Basic tests / TravisCI integration
|
|
||||||
- Refactored extension for UIColor initializer
|
- Basic tests / TravisCI integration
|
||||||
- Changelog
|
- Refactored extension for UIColor initializer
|
||||||
|
- Changelog
|
||||||
|
|
||||||
## [1.1.1] - 2017-08-18
|
## [1.1.1] - 2017-08-18
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix optical animation issue
|
|
||||||
- Fix orientation change bug
|
- Fixed optical animation issue
|
||||||
|
- Fixed orientation change bug
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Refactor entire code
|
|
||||||
- Lint code
|
- Refactor entire code
|
||||||
|
- Lint code
|
||||||
|
|
||||||
## [1.1.0] - 2017-02-28
|
## [1.1.0] - 2017-02-28
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Allow changing gradient colors
|
|
||||||
|
- Allow changing gradient colors
|
||||||
|
|
||||||
## [1.0.0] - 2016-12-28
|
## [1.0.0] - 2016-12-28
|
||||||
|
|
||||||
- Initial release
|
- Initial release
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/fxm90/GradientLoadingBar/compare/4.0.0...main
|
||||||
[Unreleased]: https://github.com/fxm90/GradientLoadingBar/compare/2.2.3...master
|
[4.0.0]: https://github.com/fxm90/GradientLoadingBar/compare/3.0.0...4.0.0
|
||||||
[2.2.2]: https://github.com/fxm90/GradientLoadingBar/compare/2.2.2...2.2.3
|
[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
|
||||||
|
[2.3.2]: https://github.com/fxm90/GradientLoadingBar/compare/2.3.1...2.3.2
|
||||||
|
[2.3.1]: https://github.com/fxm90/GradientLoadingBar/compare/2.3.0...2.3.1
|
||||||
|
[2.3.0]: https://github.com/fxm90/GradientLoadingBar/compare/2.2.5...2.3.0
|
||||||
|
[2.2.5]: https://github.com/fxm90/GradientLoadingBar/compare/2.2.4...2.2.5
|
||||||
|
[2.2.4]: https://github.com/fxm90/GradientLoadingBar/compare/2.2.3...2.2.4
|
||||||
|
[2.2.3]: https://github.com/fxm90/GradientLoadingBar/compare/2.2.2...2.2.3
|
||||||
[2.2.2]: https://github.com/fxm90/GradientLoadingBar/compare/2.2.1...2.2.2
|
[2.2.2]: https://github.com/fxm90/GradientLoadingBar/compare/2.2.1...2.2.2
|
||||||
[2.2.1]: https://github.com/fxm90/GradientLoadingBar/compare/2.2.0...2.2.1
|
[2.2.1]: https://github.com/fxm90/GradientLoadingBar/compare/2.2.0...2.2.1
|
||||||
[2.2.0]: https://github.com/fxm90/GradientLoadingBar/compare/2.1.1...2.2.0
|
[2.2.0]: https://github.com/fxm90/GradientLoadingBar/compare/2.1.1...2.2.0
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#
|
|
||||||
# .swiftlint.yml
|
|
||||||
# Created by Felix Mau (https://felix.hamburg)
|
|
||||||
#
|
|
||||||
|
|
||||||
opt_in_rules:
|
|
||||||
- file_header
|
|
||||||
|
|
||||||
file_header:
|
|
||||||
required_pattern: |
|
|
||||||
\/\/
|
|
||||||
\/\/ SWIFTLINT_CURRENT_FILENAME
|
|
||||||
\/\/ (GradientLoadingBar|GradientLoadingBar_Example|GradientLoadingBar_Tests|GradientLoadingBar_SnapshotTests)
|
|
||||||
\/\/
|
|
||||||
\/\/ 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
|
|
||||||
|
|
||||||
@@ -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)
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
GEM
|
|
||||||
remote: https://rubygems.org/
|
|
||||||
specs:
|
|
||||||
CFPropertyList (3.0.5)
|
|
||||||
rexml
|
|
||||||
activesupport (6.1.4.1)
|
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
||||||
i18n (>= 1.6, < 2)
|
|
||||||
minitest (>= 5.1)
|
|
||||||
tzinfo (~> 2.0)
|
|
||||||
zeitwerk (~> 2.3)
|
|
||||||
addressable (2.8.0)
|
|
||||||
public_suffix (>= 2.0.2, < 5.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.539.0)
|
|
||||||
aws-sdk-core (3.124.0)
|
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
|
||||||
aws-partitions (~> 1, >= 1.525.0)
|
|
||||||
aws-sigv4 (~> 1.1)
|
|
||||||
jmespath (~> 1.0)
|
|
||||||
aws-sdk-kms (1.52.0)
|
|
||||||
aws-sdk-core (~> 3, >= 3.122.0)
|
|
||||||
aws-sigv4 (~> 1.1)
|
|
||||||
aws-sdk-s3 (1.109.0)
|
|
||||||
aws-sdk-core (~> 3, >= 3.122.0)
|
|
||||||
aws-sdk-kms (~> 1)
|
|
||||||
aws-sigv4 (~> 1.4)
|
|
||||||
aws-sigv4 (1.4.0)
|
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
|
||||||
babosa (1.0.4)
|
|
||||||
claide (1.0.3)
|
|
||||||
cocoapods (1.11.2)
|
|
||||||
addressable (~> 2.8)
|
|
||||||
claide (>= 1.0.2, < 2.0)
|
|
||||||
cocoapods-core (= 1.11.2)
|
|
||||||
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.2)
|
|
||||||
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.5.1)
|
|
||||||
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.9)
|
|
||||||
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.7.6)
|
|
||||||
emoji_regex (3.2.3)
|
|
||||||
escape (0.0.4)
|
|
||||||
ethon (0.15.0)
|
|
||||||
ffi (>= 1.15.0)
|
|
||||||
excon (0.88.0)
|
|
||||||
faraday (1.8.0)
|
|
||||||
faraday-em_http (~> 1.0)
|
|
||||||
faraday-em_synchrony (~> 1.0)
|
|
||||||
faraday-excon (~> 1.1)
|
|
||||||
faraday-httpclient (~> 1.0.1)
|
|
||||||
faraday-net_http (~> 1.0)
|
|
||||||
faraday-net_http_persistent (~> 1.1)
|
|
||||||
faraday-patron (~> 1.0)
|
|
||||||
faraday-rack (~> 1.0)
|
|
||||||
multipart-post (>= 1.2, < 3)
|
|
||||||
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-net_http (1.0.1)
|
|
||||||
faraday-net_http_persistent (1.2.0)
|
|
||||||
faraday-patron (1.0.0)
|
|
||||||
faraday-rack (1.0.0)
|
|
||||||
faraday_middleware (1.2.0)
|
|
||||||
faraday (~> 1.0)
|
|
||||||
fastimage (2.2.5)
|
|
||||||
fastlane (2.198.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.1.0)
|
|
||||||
ffi (1.15.4)
|
|
||||||
fourflusher (2.3.1)
|
|
||||||
fuzzy_match (2.0.4)
|
|
||||||
gh_inspector (1.1.3)
|
|
||||||
google-apis-androidpublisher_v3 (0.13.0)
|
|
||||||
google-apis-core (>= 0.4, < 2.a)
|
|
||||||
google-apis-core (0.4.1)
|
|
||||||
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.8.0)
|
|
||||||
google-apis-core (>= 0.4, < 2.a)
|
|
||||||
google-apis-playcustomapp_v1 (0.6.0)
|
|
||||||
google-apis-core (>= 0.4, < 2.a)
|
|
||||||
google-apis-storage_v1 (0.9.0)
|
|
||||||
google-apis-core (>= 0.4, < 2.a)
|
|
||||||
google-cloud-core (1.6.0)
|
|
||||||
google-cloud-env (~> 1.0)
|
|
||||||
google-cloud-errors (~> 1.0)
|
|
||||||
google-cloud-env (1.5.0)
|
|
||||||
faraday (>= 0.17.3, < 2.0)
|
|
||||||
google-cloud-errors (1.2.0)
|
|
||||||
google-cloud-storage (1.34.1)
|
|
||||||
addressable (~> 2.5)
|
|
||||||
digest-crc (~> 0.4)
|
|
||||||
google-apis-iamcredentials_v1 (~> 0.1)
|
|
||||||
google-apis-storage_v1 (~> 0.1)
|
|
||||||
google-cloud-core (~> 1.6)
|
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
|
||||||
mini_mime (~> 1.0)
|
|
||||||
googleauth (1.1.0)
|
|
||||||
faraday (>= 0.17.3, < 2.0)
|
|
||||||
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.4)
|
|
||||||
domain_name (~> 0.5)
|
|
||||||
httpclient (2.8.3)
|
|
||||||
i18n (1.8.11)
|
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
jmespath (1.4.0)
|
|
||||||
json (2.6.1)
|
|
||||||
jwt (2.3.0)
|
|
||||||
memoist (0.16.2)
|
|
||||||
mini_magick (4.11.0)
|
|
||||||
mini_mime (1.1.2)
|
|
||||||
minitest (5.14.4)
|
|
||||||
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.6)
|
|
||||||
rake (13.0.6)
|
|
||||||
representable (3.1.1)
|
|
||||||
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.16.0)
|
|
||||||
addressable (~> 2.8)
|
|
||||||
faraday (>= 0.17.3, < 2.0)
|
|
||||||
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.4)
|
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
uber (0.1.0)
|
|
||||||
unf (0.1.4)
|
|
||||||
unf_ext
|
|
||||||
unf_ext (0.0.8)
|
|
||||||
unicode-display_width (1.8.0)
|
|
||||||
webrick (1.7.0)
|
|
||||||
word_wrap (1.0.0)
|
|
||||||
xcodeproj (1.21.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.5.1)
|
|
||||||
|
|
||||||
PLATFORMS
|
|
||||||
ruby
|
|
||||||
|
|
||||||
DEPENDENCIES
|
|
||||||
cocoapods
|
|
||||||
fastlane
|
|
||||||
fastlane-plugin-swiftformat
|
|
||||||
|
|
||||||
BUNDLED WITH
|
|
||||||
2.1.4
|
|
||||||
@@ -1,903 +0,0 @@
|
|||||||
// !$*UTF8*$!
|
|
||||||
{
|
|
||||||
archiveVersion = 1;
|
|
||||||
classes = {
|
|
||||||
};
|
|
||||||
objectVersion = 46;
|
|
||||||
objects = {
|
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
|
||||||
397E9F2412E802485B755A87 /* Pods_GradientLoadingBar_SnapshotTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABA9B1EF5F3928BA6E31D3EA /* Pods_GradientLoadingBar_SnapshotTests.framework */; };
|
|
||||||
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
|
|
||||||
607FACD81AFB9204008FA782 /* BasicExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* BasicExampleViewController.swift */; };
|
|
||||||
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
|
|
||||||
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
|
|
||||||
640E8AC0231457620008C5A6 /* GradientActivityIndicatorViewModelTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640E8ABF231457620008C5A6 /* GradientActivityIndicatorViewModelTestCase.swift */; };
|
|
||||||
6430C3A32338F1CA001F4A33 /* LargeNavigationBarHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6430C3A22338F1CA001F4A33 /* LargeNavigationBarHeaderView.swift */; };
|
|
||||||
649A163C23770B4400404F3C /* DefaultColorSnapshotTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649A163B23770B4400404F3C /* DefaultColorSnapshotTestCase.swift */; };
|
|
||||||
649A164423770E9300404F3C /* CustomColorSnapshotTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649A164323770E9300404F3C /* CustomColorSnapshotTestCase.swift */; };
|
|
||||||
64C596EC2351D80000A6CC97 /* InterfaceBuilderExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C596EB2351D7FF00A6CC97 /* InterfaceBuilderExampleViewController.swift */; };
|
|
||||||
64E0F5BF2211B7D20066D00D /* GradientLoadingBarViewModelTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E0F5BB2211B7D20066D00D /* GradientLoadingBarViewModelTestCase.swift */; };
|
|
||||||
64E7F4252293E16B00BE5095 /* GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E7F4242293E16B00BE5095 /* GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift */; };
|
|
||||||
77357BD34CFF2778341F80E8 /* Pods_GradientLoadingBar_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03CD5D69D7C3FBBDBF5FABBB /* Pods_GradientLoadingBar_Example.framework */; };
|
|
||||||
8020C040972E902A8CCAA9E7 /* Pods_GradientLoadingBar_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 434E3C8A6E245A38C2831E73 /* Pods_GradientLoadingBar_Tests.framework */; };
|
|
||||||
972CFBCC2138849A006F14C9 /* SafeAreaExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 972CFBCB2138849A006F14C9 /* SafeAreaExampleViewController.swift */; };
|
|
||||||
97464E6721372BCD0097183A /* AdvancedExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97464E6621372BCD0097183A /* AdvancedExampleViewController.swift */; };
|
|
||||||
97464E6921372C010097183A /* NavigationBarExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97464E6821372C010097183A /* NavigationBarExampleViewController.swift */; };
|
|
||||||
97B6F5FE2143F31F008CFEA5 /* UIColor+CustomColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97B6F5FA2143F31F008CFEA5 /* UIColor+CustomColors.swift */; };
|
|
||||||
97B6F5FF2143F31F008CFEA5 /* BlueFilledButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97B6F5FC2143F31F008CFEA5 /* BlueFilledButton.swift */; };
|
|
||||||
97B6F6002143F31F008CFEA5 /* BlueBorderedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97B6F5FD2143F31F008CFEA5 /* BlueBorderedButton.swift */; };
|
|
||||||
DC190F622496A25A0069E2B0 /* NotchGradientLoadingBarControllerTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC190F602496A24F0069E2B0 /* NotchGradientLoadingBarControllerTestCase.swift */; };
|
|
||||||
DC9FF938270A470D0065B5B9 /* NotchGradientLoadingBarViewModelTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9FF937270A470D0065B5B9 /* NotchGradientLoadingBarViewModelTestCase.swift */; };
|
|
||||||
DCE3AB13249761B300889FFA /* Snapshotting+WindowedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE3AB11249761AB00889FFA /* Snapshotting+WindowedImage.swift */; };
|
|
||||||
DCE3AB16249764BB00889FFA /* GradientLoadingBarControllerTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE3AB14249764A000889FFA /* GradientLoadingBarControllerTestCase.swift */; };
|
|
||||||
/* End PBXBuildFile section */
|
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
|
||||||
607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = {
|
|
||||||
isa = PBXContainerItemProxy;
|
|
||||||
containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
|
|
||||||
proxyType = 1;
|
|
||||||
remoteGlobalIDString = 607FACCF1AFB9204008FA782;
|
|
||||||
remoteInfo = GradientLoadingBar;
|
|
||||||
};
|
|
||||||
649A163E23770B4400404F3C /* PBXContainerItemProxy */ = {
|
|
||||||
isa = PBXContainerItemProxy;
|
|
||||||
containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
|
|
||||||
proxyType = 1;
|
|
||||||
remoteGlobalIDString = 607FACCF1AFB9204008FA782;
|
|
||||||
remoteInfo = GradientLoadingBar_Example;
|
|
||||||
};
|
|
||||||
/* End PBXContainerItemProxy section */
|
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
|
||||||
03CD5D69D7C3FBBDBF5FABBB /* Pods_GradientLoadingBar_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GradientLoadingBar_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
0E24EC094BE0798D462B5F96 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
|
|
||||||
14959BE5652C9E83FC09BBDD /* Pods-GradientLoadingBar_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GradientLoadingBar_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-GradientLoadingBar_Tests/Pods-GradientLoadingBar_Tests.release.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
172FD3755BEBFF66683F5D81 /* Pods-GradientLoadingBar_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GradientLoadingBar_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-GradientLoadingBar_Example/Pods-GradientLoadingBar_Example.release.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
3DC26E6D2B0C63183F508578 /* Pods-GradientLoadingBar_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GradientLoadingBar_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GradientLoadingBar_Example/Pods-GradientLoadingBar_Example.debug.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
434E3C8A6E245A38C2831E73 /* Pods_GradientLoadingBar_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GradientLoadingBar_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
607FACD01AFB9204008FA782 /* GradientLoadingBar_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GradientLoadingBar_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
|
||||||
607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
|
||||||
607FACD71AFB9204008FA782 /* BasicExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicExampleViewController.swift; sourceTree = "<group>"; };
|
|
||||||
607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
|
||||||
607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
|
||||||
607FACE51AFB9204008FA782 /* GradientLoadingBar_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GradientLoadingBar_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
|
||||||
640E8ABF231457620008C5A6 /* GradientActivityIndicatorViewModelTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientActivityIndicatorViewModelTestCase.swift; sourceTree = "<group>"; };
|
|
||||||
6430C3A22338F1CA001F4A33 /* LargeNavigationBarHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeNavigationBarHeaderView.swift; sourceTree = "<group>"; };
|
|
||||||
649A163923770B4400404F3C /* GradientLoadingBar_SnapshotTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GradientLoadingBar_SnapshotTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
649A163B23770B4400404F3C /* DefaultColorSnapshotTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultColorSnapshotTestCase.swift; sourceTree = "<group>"; };
|
|
||||||
649A163D23770B4400404F3C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
|
||||||
649A164323770E9300404F3C /* CustomColorSnapshotTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomColorSnapshotTestCase.swift; sourceTree = "<group>"; };
|
|
||||||
64C596EB2351D7FF00A6CC97 /* InterfaceBuilderExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterfaceBuilderExampleViewController.swift; sourceTree = "<group>"; };
|
|
||||||
64E0F5BB2211B7D20066D00D /* GradientLoadingBarViewModelTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradientLoadingBarViewModelTestCase.swift; sourceTree = "<group>"; };
|
|
||||||
64E7F4242293E16B00BE5095 /* GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift"; sourceTree = "<group>"; };
|
|
||||||
972CFBCB2138849A006F14C9 /* SafeAreaExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafeAreaExampleViewController.swift; sourceTree = "<group>"; };
|
|
||||||
97464E6621372BCD0097183A /* AdvancedExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedExampleViewController.swift; sourceTree = "<group>"; };
|
|
||||||
97464E6821372C010097183A /* NavigationBarExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarExampleViewController.swift; sourceTree = "<group>"; };
|
|
||||||
97B6F5FA2143F31F008CFEA5 /* UIColor+CustomColors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+CustomColors.swift"; sourceTree = "<group>"; };
|
|
||||||
97B6F5FC2143F31F008CFEA5 /* BlueFilledButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlueFilledButton.swift; sourceTree = "<group>"; };
|
|
||||||
97B6F5FD2143F31F008CFEA5 /* BlueBorderedButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlueBorderedButton.swift; sourceTree = "<group>"; };
|
|
||||||
A98CA9F1B0C1BD6864C14E01 /* Pods-GradientLoadingBar_SnapshotTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GradientLoadingBar_SnapshotTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-GradientLoadingBar_SnapshotTests/Pods-GradientLoadingBar_SnapshotTests.release.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
ABA9B1EF5F3928BA6E31D3EA /* Pods_GradientLoadingBar_SnapshotTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GradientLoadingBar_SnapshotTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
C06CA4DFA1DCB931095ABD3D /* GradientLoadingBar.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = GradientLoadingBar.podspec; path = ../GradientLoadingBar.podspec; sourceTree = "<group>"; };
|
|
||||||
DC190F602496A24F0069E2B0 /* NotchGradientLoadingBarControllerTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchGradientLoadingBarControllerTestCase.swift; sourceTree = "<group>"; };
|
|
||||||
DC9FF937270A470D0065B5B9 /* NotchGradientLoadingBarViewModelTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchGradientLoadingBarViewModelTestCase.swift; sourceTree = "<group>"; };
|
|
||||||
DCE3AB11249761AB00889FFA /* Snapshotting+WindowedImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Snapshotting+WindowedImage.swift"; sourceTree = "<group>"; };
|
|
||||||
DCE3AB14249764A000889FFA /* GradientLoadingBarControllerTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientLoadingBarControllerTestCase.swift; sourceTree = "<group>"; };
|
|
||||||
DCE3AB172497670400889FFA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
|
||||||
E7B292793F03D669ACAA92D2 /* Pods-GradientLoadingBar_SnapshotTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GradientLoadingBar_SnapshotTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GradientLoadingBar_SnapshotTests/Pods-GradientLoadingBar_SnapshotTests.debug.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
F76834BE9E43CA11377EC030 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
|
|
||||||
FECB95A4857728345199461F /* Pods-GradientLoadingBar_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GradientLoadingBar_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GradientLoadingBar_Tests/Pods-GradientLoadingBar_Tests.debug.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
|
||||||
607FACCD1AFB9204008FA782 /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
77357BD34CFF2778341F80E8 /* Pods_GradientLoadingBar_Example.framework in Frameworks */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
607FACE21AFB9204008FA782 /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
8020C040972E902A8CCAA9E7 /* Pods_GradientLoadingBar_Tests.framework in Frameworks */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
649A163623770B4400404F3C /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
397E9F2412E802485B755A87 /* Pods_GradientLoadingBar_SnapshotTests.framework in Frameworks */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXFrameworksBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
|
||||||
607FACC71AFB9204008FA782 = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
607FACF51AFB993E008FA782 /* Podspec Metadata */,
|
|
||||||
607FACD21AFB9204008FA782 /* Example for GradientLoadingBar */,
|
|
||||||
607FACE81AFB9204008FA782 /* Tests */,
|
|
||||||
649A163A23770B4400404F3C /* SnapshotTests */,
|
|
||||||
607FACD11AFB9204008FA782 /* Products */,
|
|
||||||
BFC08F9E633527E553EDA250 /* Pods */,
|
|
||||||
DB185876CEF7C6517EB25B58 /* Frameworks */,
|
|
||||||
);
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
607FACD11AFB9204008FA782 /* Products */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
607FACD01AFB9204008FA782 /* GradientLoadingBar_Example.app */,
|
|
||||||
607FACE51AFB9204008FA782 /* GradientLoadingBar_Tests.xctest */,
|
|
||||||
649A163923770B4400404F3C /* GradientLoadingBar_SnapshotTests.xctest */,
|
|
||||||
);
|
|
||||||
name = Products;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
607FACD21AFB9204008FA782 /* Example for GradientLoadingBar */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
97B6F5F92143F31F008CFEA5 /* Extensions */,
|
|
||||||
97B6F5FB2143F31F008CFEA5 /* Views */,
|
|
||||||
607FACD71AFB9204008FA782 /* BasicExampleViewController.swift */,
|
|
||||||
972CFBCB2138849A006F14C9 /* SafeAreaExampleViewController.swift */,
|
|
||||||
64C596EB2351D7FF00A6CC97 /* InterfaceBuilderExampleViewController.swift */,
|
|
||||||
97464E6621372BCD0097183A /* AdvancedExampleViewController.swift */,
|
|
||||||
97464E6821372C010097183A /* NavigationBarExampleViewController.swift */,
|
|
||||||
607FACD51AFB9204008FA782 /* AppDelegate.swift */,
|
|
||||||
607FACD91AFB9204008FA782 /* Main.storyboard */,
|
|
||||||
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */,
|
|
||||||
607FACD31AFB9204008FA782 /* Supporting Files */,
|
|
||||||
);
|
|
||||||
name = "Example for GradientLoadingBar";
|
|
||||||
path = GradientLoadingBar;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
607FACD31AFB9204008FA782 /* Supporting Files */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
607FACD41AFB9204008FA782 /* Info.plist */,
|
|
||||||
);
|
|
||||||
name = "Supporting Files";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
607FACE81AFB9204008FA782 /* Tests */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
64C6B3E72313259500076A69 /* ViewModel */,
|
|
||||||
64E7F4222293E16B00BE5095 /* Views */,
|
|
||||||
607FACE91AFB9204008FA782 /* Supporting Files */,
|
|
||||||
);
|
|
||||||
path = Tests;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
607FACE91AFB9204008FA782 /* Supporting Files */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
607FACEA1AFB9204008FA782 /* Info.plist */,
|
|
||||||
);
|
|
||||||
name = "Supporting Files";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
607FACF51AFB993E008FA782 /* Podspec Metadata */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
C06CA4DFA1DCB931095ABD3D /* GradientLoadingBar.podspec */,
|
|
||||||
0E24EC094BE0798D462B5F96 /* README.md */,
|
|
||||||
F76834BE9E43CA11377EC030 /* LICENSE */,
|
|
||||||
);
|
|
||||||
name = "Podspec Metadata";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
649A163A23770B4400404F3C /* SnapshotTests */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
DCE3AB102497619600889FFA /* _Helper */,
|
|
||||||
DC190F5E2496A2290069E2B0 /* GradientActivityIndicatorView */,
|
|
||||||
DC190F5F2496A2380069E2B0 /* GradientLoadingBarController */,
|
|
||||||
649A163D23770B4400404F3C /* Info.plist */,
|
|
||||||
DCE3AB172497670400889FFA /* README.md */,
|
|
||||||
);
|
|
||||||
path = SnapshotTests;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
64C6B3E72313259500076A69 /* ViewModel */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
64E0F5BB2211B7D20066D00D /* GradientLoadingBarViewModelTestCase.swift */,
|
|
||||||
640E8ABF231457620008C5A6 /* GradientActivityIndicatorViewModelTestCase.swift */,
|
|
||||||
DC9FF937270A470D0065B5B9 /* NotchGradientLoadingBarViewModelTestCase.swift */,
|
|
||||||
);
|
|
||||||
path = ViewModel;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
64E7F4222293E16B00BE5095 /* Views */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
64E7F4242293E16B00BE5095 /* GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift */,
|
|
||||||
);
|
|
||||||
path = Views;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
97B6F5F92143F31F008CFEA5 /* Extensions */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
97B6F5FA2143F31F008CFEA5 /* UIColor+CustomColors.swift */,
|
|
||||||
);
|
|
||||||
path = Extensions;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
97B6F5FB2143F31F008CFEA5 /* Views */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
97B6F5FC2143F31F008CFEA5 /* BlueFilledButton.swift */,
|
|
||||||
97B6F5FD2143F31F008CFEA5 /* BlueBorderedButton.swift */,
|
|
||||||
6430C3A22338F1CA001F4A33 /* LargeNavigationBarHeaderView.swift */,
|
|
||||||
);
|
|
||||||
path = Views;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
BFC08F9E633527E553EDA250 /* Pods */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
3DC26E6D2B0C63183F508578 /* Pods-GradientLoadingBar_Example.debug.xcconfig */,
|
|
||||||
172FD3755BEBFF66683F5D81 /* Pods-GradientLoadingBar_Example.release.xcconfig */,
|
|
||||||
FECB95A4857728345199461F /* Pods-GradientLoadingBar_Tests.debug.xcconfig */,
|
|
||||||
14959BE5652C9E83FC09BBDD /* Pods-GradientLoadingBar_Tests.release.xcconfig */,
|
|
||||||
E7B292793F03D669ACAA92D2 /* Pods-GradientLoadingBar_SnapshotTests.debug.xcconfig */,
|
|
||||||
A98CA9F1B0C1BD6864C14E01 /* Pods-GradientLoadingBar_SnapshotTests.release.xcconfig */,
|
|
||||||
);
|
|
||||||
name = Pods;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
DB185876CEF7C6517EB25B58 /* Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
03CD5D69D7C3FBBDBF5FABBB /* Pods_GradientLoadingBar_Example.framework */,
|
|
||||||
434E3C8A6E245A38C2831E73 /* Pods_GradientLoadingBar_Tests.framework */,
|
|
||||||
ABA9B1EF5F3928BA6E31D3EA /* Pods_GradientLoadingBar_SnapshotTests.framework */,
|
|
||||||
);
|
|
||||||
name = Frameworks;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
DC190F5E2496A2290069E2B0 /* GradientActivityIndicatorView */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
649A163B23770B4400404F3C /* DefaultColorSnapshotTestCase.swift */,
|
|
||||||
649A164323770E9300404F3C /* CustomColorSnapshotTestCase.swift */,
|
|
||||||
);
|
|
||||||
path = GradientActivityIndicatorView;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
DC190F5F2496A2380069E2B0 /* GradientLoadingBarController */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
DCE3AB14249764A000889FFA /* GradientLoadingBarControllerTestCase.swift */,
|
|
||||||
DC190F602496A24F0069E2B0 /* NotchGradientLoadingBarControllerTestCase.swift */,
|
|
||||||
);
|
|
||||||
path = GradientLoadingBarController;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
DCE3AB102497619600889FFA /* _Helper */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
DCE3AB11249761AB00889FFA /* Snapshotting+WindowedImage.swift */,
|
|
||||||
);
|
|
||||||
path = _Helper;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
|
||||||
607FACCF1AFB9204008FA782 /* GradientLoadingBar_Example */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "GradientLoadingBar_Example" */;
|
|
||||||
buildPhases = (
|
|
||||||
8A80B9B4778F0A3D30371790 /* [CP] Check Pods Manifest.lock */,
|
|
||||||
607FACCC1AFB9204008FA782 /* Sources */,
|
|
||||||
607FACCD1AFB9204008FA782 /* Frameworks */,
|
|
||||||
607FACCE1AFB9204008FA782 /* Resources */,
|
|
||||||
1A66A3C243446BE65EBE3AAE /* [CP] Embed Pods Frameworks */,
|
|
||||||
972EE1C9201CA5C4001F603C /* Swiftformat */,
|
|
||||||
9753E83D1F80F7D300EF2822 /* Swiftlint */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
);
|
|
||||||
name = GradientLoadingBar_Example;
|
|
||||||
productName = GradientLoadingBar;
|
|
||||||
productReference = 607FACD01AFB9204008FA782 /* GradientLoadingBar_Example.app */;
|
|
||||||
productType = "com.apple.product-type.application";
|
|
||||||
};
|
|
||||||
607FACE41AFB9204008FA782 /* GradientLoadingBar_Tests */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "GradientLoadingBar_Tests" */;
|
|
||||||
buildPhases = (
|
|
||||||
04A6606B8779F59C5BA953A7 /* [CP] Check Pods Manifest.lock */,
|
|
||||||
607FACE11AFB9204008FA782 /* Sources */,
|
|
||||||
607FACE21AFB9204008FA782 /* Frameworks */,
|
|
||||||
607FACE31AFB9204008FA782 /* Resources */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
607FACE71AFB9204008FA782 /* PBXTargetDependency */,
|
|
||||||
);
|
|
||||||
name = GradientLoadingBar_Tests;
|
|
||||||
productName = Tests;
|
|
||||||
productReference = 607FACE51AFB9204008FA782 /* GradientLoadingBar_Tests.xctest */;
|
|
||||||
productType = "com.apple.product-type.bundle.unit-test";
|
|
||||||
};
|
|
||||||
649A163823770B4400404F3C /* GradientLoadingBar_SnapshotTests */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = 649A164223770B4400404F3C /* Build configuration list for PBXNativeTarget "GradientLoadingBar_SnapshotTests" */;
|
|
||||||
buildPhases = (
|
|
||||||
C6F0F8135CCFB10901DB76DB /* [CP] Check Pods Manifest.lock */,
|
|
||||||
649A163523770B4400404F3C /* Sources */,
|
|
||||||
649A163623770B4400404F3C /* Frameworks */,
|
|
||||||
649A163723770B4400404F3C /* Resources */,
|
|
||||||
568034A03F733A9FAEAC587B /* [CP] Embed Pods Frameworks */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
649A163F23770B4400404F3C /* PBXTargetDependency */,
|
|
||||||
);
|
|
||||||
name = GradientLoadingBar_SnapshotTests;
|
|
||||||
productName = SnapshotTests;
|
|
||||||
productReference = 649A163923770B4400404F3C /* GradientLoadingBar_SnapshotTests.xctest */;
|
|
||||||
productType = "com.apple.product-type.bundle.unit-test";
|
|
||||||
};
|
|
||||||
/* End PBXNativeTarget section */
|
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
|
||||||
607FACC81AFB9204008FA782 /* Project object */ = {
|
|
||||||
isa = PBXProject;
|
|
||||||
attributes = {
|
|
||||||
LastSwiftUpdateCheck = 1120;
|
|
||||||
LastUpgradeCheck = 1020;
|
|
||||||
ORGANIZATIONNAME = CocoaPods;
|
|
||||||
TargetAttributes = {
|
|
||||||
607FACCF1AFB9204008FA782 = {
|
|
||||||
CreatedOnToolsVersion = 6.3.1;
|
|
||||||
LastSwiftMigration = 1020;
|
|
||||||
};
|
|
||||||
607FACE41AFB9204008FA782 = {
|
|
||||||
CreatedOnToolsVersion = 6.3.1;
|
|
||||||
LastSwiftMigration = 1020;
|
|
||||||
TestTargetID = 607FACCF1AFB9204008FA782;
|
|
||||||
};
|
|
||||||
649A163823770B4400404F3C = {
|
|
||||||
CreatedOnToolsVersion = 11.2;
|
|
||||||
ProvisioningStyle = Automatic;
|
|
||||||
TestTargetID = 607FACCF1AFB9204008FA782;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "GradientLoadingBar" */;
|
|
||||||
compatibilityVersion = "Xcode 3.2";
|
|
||||||
developmentRegion = en;
|
|
||||||
hasScannedForEncodings = 0;
|
|
||||||
knownRegions = (
|
|
||||||
en,
|
|
||||||
Base,
|
|
||||||
);
|
|
||||||
mainGroup = 607FACC71AFB9204008FA782;
|
|
||||||
productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
|
|
||||||
projectDirPath = "";
|
|
||||||
projectRoot = "";
|
|
||||||
targets = (
|
|
||||||
607FACCF1AFB9204008FA782 /* GradientLoadingBar_Example */,
|
|
||||||
607FACE41AFB9204008FA782 /* GradientLoadingBar_Tests */,
|
|
||||||
649A163823770B4400404F3C /* GradientLoadingBar_SnapshotTests */,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
/* End PBXProject section */
|
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
|
||||||
607FACCE1AFB9204008FA782 /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */,
|
|
||||||
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
607FACE31AFB9204008FA782 /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
649A163723770B4400404F3C /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXResourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
|
||||||
04A6606B8779F59C5BA953A7 /* [CP] Check Pods Manifest.lock */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
|
||||||
"${PODS_ROOT}/Manifest.lock",
|
|
||||||
);
|
|
||||||
name = "[CP] Check Pods Manifest.lock";
|
|
||||||
outputPaths = (
|
|
||||||
"$(DERIVED_FILE_DIR)/Pods-GradientLoadingBar_Tests-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;
|
|
||||||
};
|
|
||||||
1A66A3C243446BE65EBE3AAE /* [CP] Embed Pods Frameworks */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-GradientLoadingBar_Example/Pods-GradientLoadingBar_Example-frameworks.sh",
|
|
||||||
"${BUILT_PRODUCTS_DIR}/GradientLoadingBar/GradientLoadingBar.framework",
|
|
||||||
"${BUILT_PRODUCTS_DIR}/LightweightObservable/LightweightObservable.framework",
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
|
||||||
outputPaths = (
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GradientLoadingBar.framework",
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LightweightObservable.framework",
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GradientLoadingBar_Example/Pods-GradientLoadingBar_Example-frameworks.sh\"\n";
|
|
||||||
showEnvVarsInLog = 0;
|
|
||||||
};
|
|
||||||
568034A03F733A9FAEAC587B /* [CP] Embed Pods Frameworks */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-GradientLoadingBar_SnapshotTests/Pods-GradientLoadingBar_SnapshotTests-frameworks.sh",
|
|
||||||
"${BUILT_PRODUCTS_DIR}/SnapshotTesting/SnapshotTesting.framework",
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
|
||||||
outputPaths = (
|
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapshotTesting.framework",
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GradientLoadingBar_SnapshotTests/Pods-GradientLoadingBar_SnapshotTests-frameworks.sh\"\n";
|
|
||||||
showEnvVarsInLog = 0;
|
|
||||||
};
|
|
||||||
8A80B9B4778F0A3D30371790 /* [CP] Check Pods Manifest.lock */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
|
||||||
"${PODS_ROOT}/Manifest.lock",
|
|
||||||
);
|
|
||||||
name = "[CP] Check Pods Manifest.lock";
|
|
||||||
outputPaths = (
|
|
||||||
"$(DERIVED_FILE_DIR)/Pods-GradientLoadingBar_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;
|
|
||||||
};
|
|
||||||
972EE1C9201CA5C4001F603C /* Swiftformat */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = Swiftformat;
|
|
||||||
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";
|
|
||||||
};
|
|
||||||
9753E83D1F80F7D300EF2822 /* Swiftlint */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = Swiftlint;
|
|
||||||
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";
|
|
||||||
};
|
|
||||||
C6F0F8135CCFB10901DB76DB /* [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-GradientLoadingBar_SnapshotTests-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;
|
|
||||||
};
|
|
||||||
/* End PBXShellScriptBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
|
||||||
607FACCC1AFB9204008FA782 /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
607FACD81AFB9204008FA782 /* BasicExampleViewController.swift in Sources */,
|
|
||||||
64C596EC2351D80000A6CC97 /* InterfaceBuilderExampleViewController.swift in Sources */,
|
|
||||||
972CFBCC2138849A006F14C9 /* SafeAreaExampleViewController.swift in Sources */,
|
|
||||||
97464E6721372BCD0097183A /* AdvancedExampleViewController.swift in Sources */,
|
|
||||||
6430C3A32338F1CA001F4A33 /* LargeNavigationBarHeaderView.swift in Sources */,
|
|
||||||
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
|
|
||||||
97B6F5FF2143F31F008CFEA5 /* BlueFilledButton.swift in Sources */,
|
|
||||||
97B6F5FE2143F31F008CFEA5 /* UIColor+CustomColors.swift in Sources */,
|
|
||||||
97B6F6002143F31F008CFEA5 /* BlueBorderedButton.swift in Sources */,
|
|
||||||
97464E6921372C010097183A /* NavigationBarExampleViewController.swift in Sources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
607FACE11AFB9204008FA782 /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
640E8AC0231457620008C5A6 /* GradientActivityIndicatorViewModelTestCase.swift in Sources */,
|
|
||||||
64E0F5BF2211B7D20066D00D /* GradientLoadingBarViewModelTestCase.swift in Sources */,
|
|
||||||
DC9FF938270A470D0065B5B9 /* NotchGradientLoadingBarViewModelTestCase.swift in Sources */,
|
|
||||||
64E7F4252293E16B00BE5095 /* GradientActivityIndicatorView+AnimateIsHiddenTestCase.swift in Sources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
649A163523770B4400404F3C /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
DCE3AB13249761B300889FFA /* Snapshotting+WindowedImage.swift in Sources */,
|
|
||||||
DC190F622496A25A0069E2B0 /* NotchGradientLoadingBarControllerTestCase.swift in Sources */,
|
|
||||||
DCE3AB16249764BB00889FFA /* GradientLoadingBarControllerTestCase.swift in Sources */,
|
|
||||||
649A164423770E9300404F3C /* CustomColorSnapshotTestCase.swift in Sources */,
|
|
||||||
649A163C23770B4400404F3C /* DefaultColorSnapshotTestCase.swift in Sources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXSourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
|
||||||
607FACE71AFB9204008FA782 /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
|
||||||
target = 607FACCF1AFB9204008FA782 /* GradientLoadingBar_Example */;
|
|
||||||
targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
649A163F23770B4400404F3C /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
|
||||||
target = 607FACCF1AFB9204008FA782 /* GradientLoadingBar_Example */;
|
|
||||||
targetProxy = 649A163E23770B4400404F3C /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
/* End PBXTargetDependency section */
|
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
|
||||||
607FACD91AFB9204008FA782 /* Main.storyboard */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
607FACDA1AFB9204008FA782 /* Base */,
|
|
||||||
);
|
|
||||||
name = Main.storyboard;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
607FACDF1AFB9204008FA782 /* Base */,
|
|
||||||
);
|
|
||||||
name = LaunchScreen.xib;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXVariantGroup section */
|
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
|
||||||
607FACED1AFB9204008FA782 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = 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_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_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
ENABLE_TESTABILITY = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
|
||||||
"DEBUG=1",
|
|
||||||
"$(inherited)",
|
|
||||||
);
|
|
||||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
|
||||||
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 = 9.0;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
607FACEE1AFB9204008FA782 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
|
||||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
CLANG_ENABLE_OBJC_ARC = 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_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_RANGE_LOOP_ANALYSIS = YES;
|
|
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
|
||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
|
||||||
COPY_PHASE_STRIP = NO;
|
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
|
||||||
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 = 9.0;
|
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
|
||||||
SDKROOT = iphoneos;
|
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
|
||||||
VALIDATE_PRODUCT = YES;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
607FACF01AFB9204008FA782 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = 3DC26E6D2B0C63183F508578 /* Pods-GradientLoadingBar_Example.debug.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
INFOPLIST_FILE = GradientLoadingBar/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
|
||||||
MODULE_NAME = ExampleApp;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
607FACF11AFB9204008FA782 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = 172FD3755BEBFF66683F5D81 /* Pods-GradientLoadingBar_Example.release.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
|
||||||
INFOPLIST_FILE = GradientLoadingBar/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
|
||||||
MODULE_NAME = ExampleApp;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
607FACF31AFB9204008FA782 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = FECB95A4857728345199461F /* Pods-GradientLoadingBar_Tests.debug.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
|
||||||
"DEBUG=1",
|
|
||||||
"$(inherited)",
|
|
||||||
);
|
|
||||||
INFOPLIST_FILE = Tests/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GradientLoadingBar_Example.app/GradientLoadingBar_Example";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
607FACF41AFB9204008FA782 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = 14959BE5652C9E83FC09BBDD /* Pods-GradientLoadingBar_Tests.release.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
|
||||||
INFOPLIST_FILE = Tests/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GradientLoadingBar_Example.app/GradientLoadingBar_Example";
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
649A164023770B4400404F3C /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = E7B292793F03D669ACAA92D2 /* Pods-GradientLoadingBar_SnapshotTests.debug.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
|
||||||
INFOPLIST_FILE = SnapshotTests/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = hamburg.felix.SnapshotTests;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GradientLoadingBar_Example.app/GradientLoadingBar_Example";
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
649A164123770B4400404F3C /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
baseConfigurationReference = A98CA9F1B0C1BD6864C14E01 /* Pods-GradientLoadingBar_SnapshotTests.release.xcconfig */;
|
|
||||||
buildSettings = {
|
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
|
||||||
INFOPLIST_FILE = SnapshotTests/Info.plist;
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.2;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
|
||||||
MTL_FAST_MATH = YES;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = hamburg.felix.SnapshotTests;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GradientLoadingBar_Example.app/GradientLoadingBar_Example";
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
/* End XCBuildConfiguration section */
|
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
|
||||||
607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "GradientLoadingBar" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
607FACED1AFB9204008FA782 /* Debug */,
|
|
||||||
607FACEE1AFB9204008FA782 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "GradientLoadingBar_Example" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
607FACF01AFB9204008FA782 /* Debug */,
|
|
||||||
607FACF11AFB9204008FA782 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "GradientLoadingBar_Tests" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
607FACF31AFB9204008FA782 /* Debug */,
|
|
||||||
607FACF41AFB9204008FA782 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
649A164223770B4400404F3C /* Build configuration list for PBXNativeTarget "GradientLoadingBar_SnapshotTests" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
649A164023770B4400404F3C /* Debug */,
|
|
||||||
649A164123770B4400404F3C /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
/* End XCConfigurationList section */
|
|
||||||
};
|
|
||||||
rootObject = 607FACC81AFB9204008FA782 /* Project object */;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Workspace
|
|
||||||
version = "1.0">
|
|
||||||
<FileRef
|
|
||||||
location = "self:GradientLoadingBar.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Scheme
|
|
||||||
LastUpgradeVersion = "1020"
|
|
||||||
version = "1.3">
|
|
||||||
<BuildAction
|
|
||||||
parallelizeBuildables = "YES"
|
|
||||||
buildImplicitDependencies = "YES">
|
|
||||||
<BuildActionEntries>
|
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "YES"
|
|
||||||
buildForArchiving = "YES"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
|
||||||
BuildableName = "GradientLoadingBar_Example.app"
|
|
||||||
BlueprintName = "GradientLoadingBar_Example"
|
|
||||||
ReferencedContainer = "container:GradientLoadingBar.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "NO"
|
|
||||||
buildForArchiving = "NO"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "607FACE41AFB9204008FA782"
|
|
||||||
BuildableName = "GradientLoadingBar_Tests.xctest"
|
|
||||||
BlueprintName = "GradientLoadingBar_Tests"
|
|
||||||
ReferencedContainer = "container:GradientLoadingBar.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
|
||||||
</BuildAction>
|
|
||||||
<TestAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
codeCoverageEnabled = "YES"
|
|
||||||
onlyGenerateCoverageForSpecifiedTargets = "YES">
|
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
|
||||||
BuildableName = "GradientLoadingBar_Example.app"
|
|
||||||
BlueprintName = "GradientLoadingBar_Example"
|
|
||||||
ReferencedContainer = "container:GradientLoadingBar.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<CodeCoverageTargets>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "AD9297F9E6CC3E50EDCFECA0A77800B7"
|
|
||||||
BuildableName = "GradientLoadingBar.framework"
|
|
||||||
BlueprintName = "GradientLoadingBar"
|
|
||||||
ReferencedContainer = "container:Pods/Pods.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</CodeCoverageTargets>
|
|
||||||
<Testables>
|
|
||||||
<TestableReference
|
|
||||||
skipped = "NO">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "607FACE41AFB9204008FA782"
|
|
||||||
BuildableName = "GradientLoadingBar_Tests.xctest"
|
|
||||||
BlueprintName = "GradientLoadingBar_Tests"
|
|
||||||
ReferencedContainer = "container:GradientLoadingBar.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</TestableReference>
|
|
||||||
<TestableReference
|
|
||||||
skipped = "NO">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "649A163823770B4400404F3C"
|
|
||||||
BuildableName = "GradientLoadingBar_SnapshotTests.xctest"
|
|
||||||
BlueprintName = "GradientLoadingBar_SnapshotTests"
|
|
||||||
ReferencedContainer = "container:GradientLoadingBar.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</TestableReference>
|
|
||||||
</Testables>
|
|
||||||
</TestAction>
|
|
||||||
<LaunchAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
launchStyle = "0"
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
|
||||||
debugDocumentVersioning = "YES"
|
|
||||||
debugServiceExtension = "internal"
|
|
||||||
allowLocationSimulation = "YES">
|
|
||||||
<BuildableProductRunnable
|
|
||||||
runnableDebuggingMode = "0">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
|
||||||
BuildableName = "GradientLoadingBar_Example.app"
|
|
||||||
BlueprintName = "GradientLoadingBar_Example"
|
|
||||||
ReferencedContainer = "container:GradientLoadingBar.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildableProductRunnable>
|
|
||||||
</LaunchAction>
|
|
||||||
<ProfileAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
savedToolIdentifier = ""
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
debugDocumentVersioning = "YES">
|
|
||||||
<BuildableProductRunnable
|
|
||||||
runnableDebuggingMode = "0">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
|
|
||||||
BuildableName = "GradientLoadingBar_Example.app"
|
|
||||||
BlueprintName = "GradientLoadingBar_Example"
|
|
||||||
ReferencedContainer = "container:GradientLoadingBar.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildableProductRunnable>
|
|
||||||
</ProfileAction>
|
|
||||||
<AnalyzeAction
|
|
||||||
buildConfiguration = "Debug">
|
|
||||||
</AnalyzeAction>
|
|
||||||
<ArchiveAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
revealArchiveInOrganizer = "YES">
|
|
||||||
</ArchiveAction>
|
|
||||||
</Scheme>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Workspace
|
|
||||||
version = "1.0">
|
|
||||||
<FileRef
|
|
||||||
location = "group:GradientLoadingBar.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
<FileRef
|
|
||||||
location = "group:Pods/Pods.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IDEDidComputeMac32BitWarning</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
//
|
|
||||||
// AdvancedExampleViewController.swift
|
|
||||||
// GradientLoadingBar_Example
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 30.08.18.
|
|
||||||
// Copyright © 2018 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import GradientLoadingBar
|
|
||||||
|
|
||||||
class AdvancedExampleViewController: UIViewController {
|
|
||||||
// MARK: - Config
|
|
||||||
|
|
||||||
private enum Config {
|
|
||||||
/// The programatically applied height of the `GradientActivityIndicatorView`.
|
|
||||||
static let height: CGFloat = 3.5
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Outlets
|
|
||||||
|
|
||||||
@IBOutlet private var programmaticallyButton: BlueBorderedButton!
|
|
||||||
@IBOutlet private var customColorsButton: BlueBorderedButton!
|
|
||||||
|
|
||||||
// MARK: - Private properties
|
|
||||||
|
|
||||||
// swiftlint:disable:next identifier_name
|
|
||||||
private let programmaticallyGradientActivityIndicatorView = GradientActivityIndicatorView()
|
|
||||||
|
|
||||||
// swiftlint:disable:next identifier_name
|
|
||||||
private let customColorsGradientActivityIndicatorView: GradientActivityIndicatorView = {
|
|
||||||
let gradientActivityIndicatorView = GradientActivityIndicatorView()
|
|
||||||
|
|
||||||
// Source: https://color.adobe.com/Pink-Flamingo-color-theme-10343714/
|
|
||||||
gradientActivityIndicatorView.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),
|
|
||||||
]
|
|
||||||
|
|
||||||
return gradientActivityIndicatorView
|
|
||||||
}()
|
|
||||||
|
|
||||||
// MARK: - Public methods
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
setupProgrammaticallyGradientActivityIndicatorView()
|
|
||||||
setupCustomColorsGradientActivityIndicatorView()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
|
||||||
super.viewWillDisappear(animated)
|
|
||||||
|
|
||||||
programmaticallyGradientActivityIndicatorView.fadeOut()
|
|
||||||
customColorsGradientActivityIndicatorView.fadeOut()
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction private func toggleProgrammaticallyButtonTouchUpInside(_: Any) {
|
|
||||||
if programmaticallyGradientActivityIndicatorView.isHidden {
|
|
||||||
programmaticallyGradientActivityIndicatorView.fadeIn()
|
|
||||||
} else {
|
|
||||||
programmaticallyGradientActivityIndicatorView.fadeOut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction private func toggleCustomColorsButtonTouchUpInside(_: Any) {
|
|
||||||
if customColorsGradientActivityIndicatorView.isHidden {
|
|
||||||
customColorsGradientActivityIndicatorView.fadeIn()
|
|
||||||
} else {
|
|
||||||
customColorsGradientActivityIndicatorView.fadeOut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private methods
|
|
||||||
|
|
||||||
private func setupProgrammaticallyGradientActivityIndicatorView() {
|
|
||||||
programmaticallyGradientActivityIndicatorView.fadeOut(duration: 0)
|
|
||||||
|
|
||||||
programmaticallyGradientActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
programmaticallyButton.addSubview(programmaticallyGradientActivityIndicatorView)
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
programmaticallyGradientActivityIndicatorView.leadingAnchor.constraint(equalTo: programmaticallyButton.leadingAnchor),
|
|
||||||
programmaticallyGradientActivityIndicatorView.trailingAnchor.constraint(equalTo: programmaticallyButton.trailingAnchor),
|
|
||||||
|
|
||||||
programmaticallyGradientActivityIndicatorView.topAnchor.constraint(equalTo: programmaticallyButton.topAnchor),
|
|
||||||
programmaticallyGradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: Config.height),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupCustomColorsGradientActivityIndicatorView() {
|
|
||||||
customColorsGradientActivityIndicatorView.fadeOut(duration: 0)
|
|
||||||
|
|
||||||
customColorsGradientActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
customColorsButton.addSubview(customColorsGradientActivityIndicatorView)
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
customColorsGradientActivityIndicatorView.leadingAnchor.constraint(equalTo: customColorsButton.leadingAnchor),
|
|
||||||
customColorsGradientActivityIndicatorView.trailingAnchor.constraint(equalTo: customColorsButton.trailingAnchor),
|
|
||||||
|
|
||||||
customColorsGradientActivityIndicatorView.bottomAnchor.constraint(equalTo: customColorsButton.bottomAnchor),
|
|
||||||
customColorsGradientActivityIndicatorView.heightAnchor.constraint(equalToConstant: Config.height),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
//
|
|
||||||
// AppDelegate.swift
|
|
||||||
// GradientLoadingBar
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 30.09.17.
|
|
||||||
// Copyright © 2017 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import GradientLoadingBar
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
@UIApplicationMain
|
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
||||||
var window: UIWindow?
|
|
||||||
|
|
||||||
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
|
||||||
// Override point for customization after application launch.
|
|
||||||
|
|
||||||
// Example on how to customize the shared instance of `GradientLoadingBar`.
|
|
||||||
// We're gonna use the same height though ¯\_(ツ)_/¯
|
|
||||||
let customGradientLoadingBar = GradientLoadingBar(height: 3.0)
|
|
||||||
GradientLoadingBar.shared = customGradientLoadingBar
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationWillResignActive(_: UIApplication) {
|
|
||||||
// Sent when the application is about to move from active to inactive state.
|
|
||||||
// This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application
|
|
||||||
// and it begins the transition to the background state.
|
|
||||||
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationDidEnterBackground(_: UIApplication) {
|
|
||||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your
|
|
||||||
// application to its current state in case it is terminated later.
|
|
||||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationWillEnterForeground(_: UIApplication) {
|
|
||||||
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationDidBecomeActive(_: UIApplication) {
|
|
||||||
// Restart any tasks that were paused (or not yet started) while the application was inactive.
|
|
||||||
// If the application was previously in the background, optionally refresh the user interface.
|
|
||||||
}
|
|
||||||
|
|
||||||
func applicationWillTerminate(_: UIApplication) {
|
|
||||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
|
||||||
<dependencies>
|
|
||||||
<deployment identifier="iOS"/>
|
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
|
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
||||||
</dependencies>
|
|
||||||
<objects>
|
|
||||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
|
||||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Gradient Loading Bar" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iUK-He-88D">
|
|
||||||
<rect key="frame" x="20" y="219.5" width="440" height="20.5"/>
|
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Example Application" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kZE-Up-yio">
|
|
||||||
<rect key="frame" x="20" y="240" width="440" height="18"/>
|
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
|
|
||||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="kZE-Up-yio" secondAttribute="trailing" id="1w6-hQ-DuS"/>
|
|
||||||
<constraint firstItem="kZE-Up-yio" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leadingMargin" id="JLl-ee-fNq"/>
|
|
||||||
<constraint firstItem="kZE-Up-yio" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="centerY" id="b41-JE-swY"/>
|
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="iUK-He-88D" secondAttribute="trailing" id="eCr-27-PHd"/>
|
|
||||||
<constraint firstItem="iUK-He-88D" firstAttribute="bottom" secondItem="iN0-l3-epB" secondAttribute="centerY" id="g3R-8N-4ZM"/>
|
|
||||||
<constraint firstItem="iUK-He-88D" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leadingMargin" id="lzx-6Y-lwW"/>
|
|
||||||
</constraints>
|
|
||||||
<nil key="simulatedStatusBarMetrics"/>
|
|
||||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
|
||||||
<point key="canvasLocation" x="548" y="455"/>
|
|
||||||
</view>
|
|
||||||
</objects>
|
|
||||||
</document>
|
|
||||||
@@ -1,489 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="31S-8b-Niv">
|
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
|
||||||
<dependencies>
|
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
|
||||||
</dependencies>
|
|
||||||
<scenes>
|
|
||||||
<!--Basic Example-->
|
|
||||||
<scene sceneID="ufC-wZ-h7g">
|
|
||||||
<objects>
|
|
||||||
<viewController id="vXZ-lx-hvc" customClass="BasicExampleViewController" customModule="GradientLoadingBar_Example" customModuleProvider="target" sceneMemberID="viewController">
|
|
||||||
<layoutGuides>
|
|
||||||
<viewControllerLayoutGuide type="top" id="jyV-Pf-zRb"/>
|
|
||||||
<viewControllerLayoutGuide type="bottom" id="2fi-mo-0CV"/>
|
|
||||||
</layoutGuides>
|
|
||||||
<view key="view" contentMode="scaleToFill" id="kh9-bI-dsS">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="V1J-1V-cbT" customClass="LargeNavigationBarHeaderView" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="138"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="138" id="ydQ-Zv-f3W"/>
|
|
||||||
</constraints>
|
|
||||||
<userDefinedRuntimeAttributes>
|
|
||||||
<userDefinedRuntimeAttribute type="string" keyPath="title" value="🏡 Basic Example"/>
|
|
||||||
<userDefinedRuntimeAttribute type="string" keyPath="prompt" value="Gradient Loading Bar"/>
|
|
||||||
</userDefinedRuntimeAttributes>
|
|
||||||
</view>
|
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vCz-UL-eay" userLabel="Wrapper View">
|
|
||||||
<rect key="frame" x="0.0" y="138" width="375" height="480"/>
|
|
||||||
<subviews>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="Hrq-tB-Sk3">
|
|
||||||
<rect key="frame" x="16" y="220" width="343" height="40"/>
|
|
||||||
<subviews>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="W6V-cT-b8Q" customClass="BlueFilledButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="163.5" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="MdR-sO-dac"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Show"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="showButtonTouchUpInside:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="cns-MK-XRQ"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mLp-ey-yd5" customClass="BlueFilledButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="179.5" y="0.0" width="163.5" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="vge-I5-pFX"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Hide"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="hideButtonTouchUpInside:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="Cyb-6a-nJk"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="Hrq-tB-Sk3" firstAttribute="leading" secondItem="vCz-UL-eay" secondAttribute="leading" constant="16" id="Er7-gx-Ray"/>
|
|
||||||
<constraint firstItem="Hrq-tB-Sk3" firstAttribute="centerY" secondItem="vCz-UL-eay" secondAttribute="centerY" id="MB9-lc-dAX"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="Hrq-tB-Sk3" secondAttribute="trailing" constant="16" id="ao9-tR-lfs"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="V1J-1V-cbT" firstAttribute="trailing" secondItem="kh9-bI-dsS" secondAttribute="trailing" id="9TW-dR-0YF"/>
|
|
||||||
<constraint firstItem="2fi-mo-0CV" firstAttribute="top" secondItem="vCz-UL-eay" secondAttribute="bottom" id="BSE-5g-uVL"/>
|
|
||||||
<constraint firstItem="vCz-UL-eay" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" id="Lym-2g-94s"/>
|
|
||||||
<constraint firstItem="V1J-1V-cbT" firstAttribute="leading" secondItem="kh9-bI-dsS" secondAttribute="leading" id="PZZ-lR-n3R"/>
|
|
||||||
<constraint firstItem="V1J-1V-cbT" firstAttribute="top" secondItem="jyV-Pf-zRb" secondAttribute="bottom" id="VPm-eG-4vI"/>
|
|
||||||
<constraint firstItem="vCz-UL-eay" firstAttribute="top" secondItem="V1J-1V-cbT" secondAttribute="bottom" id="Vhb-09-feC"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="vCz-UL-eay" secondAttribute="trailing" id="d0u-3Y-rTF"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
<tabBarItem key="tabBarItem" title="Basic Example" image="house" catalog="system" id="QbQ-0M-Q7f"/>
|
|
||||||
<navigationItem key="navigationItem" title="Basic Example" prompt="Gradient Loading Bar" largeTitleDisplayMode="always" id="Rdw-Bd-vul"/>
|
|
||||||
</viewController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="x5A-6p-PRh" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="-969" y="940"/>
|
|
||||||
</scene>
|
|
||||||
<!--Navigation Bar Example-->
|
|
||||||
<scene sceneID="Blz-xb-a2R">
|
|
||||||
<objects>
|
|
||||||
<viewController id="sEh-Tc-lv1" userLabel="Navigation Bar Example" customClass="NavigationBarExampleViewController" customModule="GradientLoadingBar_Example" customModuleProvider="target" sceneMemberID="viewController">
|
|
||||||
<layoutGuides>
|
|
||||||
<viewControllerLayoutGuide type="top" id="Iik-d0-ufh"/>
|
|
||||||
<viewControllerLayoutGuide type="bottom" id="J9t-Vs-nTW"/>
|
|
||||||
</layoutGuides>
|
|
||||||
<view key="view" contentMode="scaleToFill" id="iwi-Ev-GiP">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wLK-vz-uDn" userLabel="Wrapper View">
|
|
||||||
<rect key="frame" x="0.0" y="138.5" width="375" height="479.5"/>
|
|
||||||
<subviews>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="xhZ-U8-Vur">
|
|
||||||
<rect key="frame" x="16" y="220" width="343" height="40"/>
|
|
||||||
<subviews>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ogf-h2-Nhm" customClass="BlueFilledButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="163.5" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="Ae3-GF-JqK"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Show"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="showButtonTouchUpInside:" destination="sEh-Tc-lv1" eventType="touchUpInside" id="8M0-h9-3np"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ZBM-PV-ZnL" customClass="BlueFilledButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="179.5" y="0.0" width="163.5" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="2cO-9h-Wp9"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Hide"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="hideButtonTouchUpInside:" destination="sEh-Tc-lv1" eventType="touchUpInside" id="oAh-2i-goU"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="xhZ-U8-Vur" secondAttribute="trailing" constant="16" id="2sW-85-0wR"/>
|
|
||||||
<constraint firstItem="xhZ-U8-Vur" firstAttribute="leading" secondItem="wLK-vz-uDn" secondAttribute="leading" constant="16" id="Al7-Jv-AJK"/>
|
|
||||||
<constraint firstItem="xhZ-U8-Vur" firstAttribute="centerY" secondItem="wLK-vz-uDn" secondAttribute="centerY" id="zTM-SW-qLa"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="wLK-vz-uDn" firstAttribute="leading" secondItem="iwi-Ev-GiP" secondAttribute="leading" id="A9Q-8d-dYD"/>
|
|
||||||
<constraint firstItem="wLK-vz-uDn" firstAttribute="top" secondItem="Iik-d0-ufh" secondAttribute="bottom" id="Pna-g3-eRn"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="wLK-vz-uDn" secondAttribute="trailing" id="Vn7-pj-36y"/>
|
|
||||||
<constraint firstItem="J9t-Vs-nTW" firstAttribute="top" secondItem="wLK-vz-uDn" secondAttribute="bottom" id="b3J-NQ-cWP"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
<navigationItem key="navigationItem" title="💡 Navigation Bar Example" prompt="– Gradient Loading Bar –" largeTitleDisplayMode="always" id="q3W-Ea-vnS"/>
|
|
||||||
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
|
|
||||||
</viewController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="3VI-no-5Tx" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="2009" y="1661"/>
|
|
||||||
</scene>
|
|
||||||
<!--Safe Area Example-->
|
|
||||||
<scene sceneID="0Ok-vd-ZTP">
|
|
||||||
<objects>
|
|
||||||
<viewController id="G0W-Ta-C7r" userLabel="Safe Area Example" customClass="SafeAreaExampleViewController" customModule="GradientLoadingBar_Example" customModuleProvider="target" sceneMemberID="viewController">
|
|
||||||
<layoutGuides>
|
|
||||||
<viewControllerLayoutGuide type="top" id="4Ae-UH-dXd"/>
|
|
||||||
<viewControllerLayoutGuide type="bottom" id="9TC-19-AMF"/>
|
|
||||||
</layoutGuides>
|
|
||||||
<view key="view" contentMode="scaleToFill" id="5Bk-7E-1bh">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tyJ-Wx-jmZ" customClass="LargeNavigationBarHeaderView" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="138"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="138" id="toR-QS-r8U"/>
|
|
||||||
</constraints>
|
|
||||||
<userDefinedRuntimeAttributes>
|
|
||||||
<userDefinedRuntimeAttribute type="string" keyPath="title" value="📲 Safe Area Example"/>
|
|
||||||
<userDefinedRuntimeAttribute type="string" keyPath="prompt" value="– Gradient Loading Bar –"/>
|
|
||||||
</userDefinedRuntimeAttributes>
|
|
||||||
</view>
|
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cQz-wE-hV5" userLabel="Wrapper View">
|
|
||||||
<rect key="frame" x="0.0" y="138" width="375" height="480"/>
|
|
||||||
<subviews>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="oWt-EZ-h2R" userLabel="Upper Stack View">
|
|
||||||
<rect key="frame" x="16" y="192" width="343" height="40"/>
|
|
||||||
<subviews>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6AO-o8-uCr" userLabel="Show Button" customClass="BlueFilledButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="163.5" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="JJW-aV-Rqk"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Show Basic Bar"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="showBasicBarButtonTouchUpInside:" destination="G0W-Ta-C7r" eventType="touchUpInside" id="Nbx-cD-Wmk"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="enr-bC-bdY" userLabel="Hide Button" customClass="BlueFilledButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="179.5" y="0.0" width="163.5" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="tlC-vL-RSX"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Hide Basic Bar"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="hideBasicBarButtonTouchUpInside:" destination="G0W-Ta-C7r" eventType="touchUpInside" id="Um3-56-3gL"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="eno-YK-fo8" userLabel="Lower Stack View">
|
|
||||||
<rect key="frame" x="16" y="248" width="343" height="40"/>
|
|
||||||
<subviews>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="oLd-Dp-Iks" customClass="BlueFilledButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="163.5" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="DIo-cN-15i"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Show Notch Bar"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="showNotchBarButtonTouchUpInside:" destination="G0W-Ta-C7r" eventType="touchUpInside" id="Q1l-9E-ova"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="S86-Vf-cv9" customClass="BlueFilledButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="179.5" y="0.0" width="163.5" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="oOd-1X-Klv"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Hide Notch Bar"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="hideNotchBarButtonTouchUpInside:" destination="G0W-Ta-C7r" eventType="touchUpInside" id="nJm-HD-YTg"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="oWt-EZ-h2R" firstAttribute="bottom" secondItem="cQz-wE-hV5" secondAttribute="centerY" constant="-8" id="77C-lL-1fS"/>
|
|
||||||
<constraint firstItem="oWt-EZ-h2R" firstAttribute="leading" secondItem="cQz-wE-hV5" secondAttribute="leading" constant="16" id="Hjd-XM-aek"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="oWt-EZ-h2R" secondAttribute="trailing" constant="16" id="ifg-oy-eiV"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="eno-YK-fo8" secondAttribute="trailing" constant="16" id="o3h-uA-jbM"/>
|
|
||||||
<constraint firstItem="eno-YK-fo8" firstAttribute="top" secondItem="cQz-wE-hV5" secondAttribute="centerY" constant="8" id="rVX-yO-z4X"/>
|
|
||||||
<constraint firstItem="eno-YK-fo8" firstAttribute="leading" secondItem="cQz-wE-hV5" secondAttribute="leading" constant="16" id="zaJ-tE-bCY"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="tyJ-Wx-jmZ" firstAttribute="leading" secondItem="5Bk-7E-1bh" secondAttribute="leading" id="BGY-Fa-Ib2"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="cQz-wE-hV5" secondAttribute="trailing" id="KmM-2A-jAw"/>
|
|
||||||
<constraint firstItem="cQz-wE-hV5" firstAttribute="top" secondItem="tyJ-Wx-jmZ" secondAttribute="bottom" id="NJ3-mJ-mLD"/>
|
|
||||||
<constraint firstItem="cQz-wE-hV5" firstAttribute="leading" secondItem="5Bk-7E-1bh" secondAttribute="leading" id="dID-N8-kru"/>
|
|
||||||
<constraint firstItem="tyJ-Wx-jmZ" firstAttribute="top" secondItem="4Ae-UH-dXd" secondAttribute="bottom" id="obq-xF-4qg"/>
|
|
||||||
<constraint firstItem="9TC-19-AMF" firstAttribute="top" secondItem="cQz-wE-hV5" secondAttribute="bottom" id="qfC-iN-Dv4"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="tyJ-Wx-jmZ" secondAttribute="trailing" id="u0E-oe-0ko"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
<tabBarItem key="tabBarItem" title="Safe Area Example" image="rectangle.and.arrow.up.right.and.arrow.down.left" catalog="system" id="VBS-L1-AHW"/>
|
|
||||||
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
|
|
||||||
</viewController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="oYv-dl-Yai" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="-214" y="940"/>
|
|
||||||
</scene>
|
|
||||||
<!--Interface Builder Example-->
|
|
||||||
<scene sceneID="NSv-IO-1Fu">
|
|
||||||
<objects>
|
|
||||||
<viewController title="Interface Builder Example" id="kcg-0d-bdI" customClass="InterfaceBuilderExampleViewController" customModule="GradientLoadingBar_Example" customModuleProvider="target" sceneMemberID="viewController">
|
|
||||||
<layoutGuides>
|
|
||||||
<viewControllerLayoutGuide type="top" id="znh-se-r1k"/>
|
|
||||||
<viewControllerLayoutGuide type="bottom" id="Ruy-Se-i9x"/>
|
|
||||||
</layoutGuides>
|
|
||||||
<view key="view" contentMode="scaleToFill" id="G47-Se-1Pm">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ei7-8i-vp4" customClass="LargeNavigationBarHeaderView" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="138"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="138" id="z0S-oU-OR8"/>
|
|
||||||
</constraints>
|
|
||||||
<userDefinedRuntimeAttributes>
|
|
||||||
<userDefinedRuntimeAttribute type="string" keyPath="title" value="🎨 Interface Builder Example"/>
|
|
||||||
<userDefinedRuntimeAttribute type="string" keyPath="prompt" value="– Gradient Loading Bar –"/>
|
|
||||||
</userDefinedRuntimeAttributes>
|
|
||||||
</view>
|
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="mbb-4g-X9Q" userLabel="Wrapper View">
|
|
||||||
<rect key="frame" x="0.0" y="138" width="375" height="480"/>
|
|
||||||
<subviews>
|
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Yze-Dk-6Ol" customClass="GradientActivityIndicatorView" customModule="GradientLoadingBar">
|
|
||||||
<rect key="frame" x="16" y="16" width="343" height="3"/>
|
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="3" id="jPq-M8-Jzb"/>
|
|
||||||
</constraints>
|
|
||||||
<userDefinedRuntimeAttributes>
|
|
||||||
<userDefinedRuntimeAttribute type="string" keyPath="layer.cornerRadius" value="3"/>
|
|
||||||
</userDefinedRuntimeAttributes>
|
|
||||||
</view>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="B2P-Ta-wQl">
|
|
||||||
<rect key="frame" x="16" y="220" width="343" height="40"/>
|
|
||||||
<subviews>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FVq-l4-0tb" customClass="BlueFilledButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="163.5" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="hLM-JF-3uL"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Show"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="showButtonTouchUpInside:" destination="kcg-0d-bdI" eventType="touchUpInside" id="bBo-X1-9o5"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FWL-hp-DMs" customClass="BlueFilledButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="179.5" y="0.0" width="163.5" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="6jt-g9-DcI"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Hide"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="hideButtonTouchUpInside:" destination="kcg-0d-bdI" eventType="touchUpInside" id="Zvk-pf-LDf"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="Yze-Dk-6Ol" secondAttribute="trailing" constant="16" id="7Ae-mT-LLE"/>
|
|
||||||
<constraint firstItem="B2P-Ta-wQl" firstAttribute="leading" secondItem="mbb-4g-X9Q" secondAttribute="leading" constant="16" id="8cK-1Z-B70"/>
|
|
||||||
<constraint firstItem="Yze-Dk-6Ol" firstAttribute="leading" secondItem="mbb-4g-X9Q" secondAttribute="leading" constant="16" id="S1n-Cp-vlD"/>
|
|
||||||
<constraint firstItem="Yze-Dk-6Ol" firstAttribute="top" secondItem="mbb-4g-X9Q" secondAttribute="top" constant="16" id="asL-T7-vTV"/>
|
|
||||||
<constraint firstItem="B2P-Ta-wQl" firstAttribute="centerY" secondItem="mbb-4g-X9Q" secondAttribute="centerY" id="p7F-Kt-bT7"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="B2P-Ta-wQl" secondAttribute="trailing" constant="16" id="tr4-w5-D3I"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="mbb-4g-X9Q" secondAttribute="trailing" id="7JI-Cf-Lbi"/>
|
|
||||||
<constraint firstItem="mbb-4g-X9Q" firstAttribute="leading" secondItem="G47-Se-1Pm" secondAttribute="leading" id="ASY-d8-7lc"/>
|
|
||||||
<constraint firstItem="mbb-4g-X9Q" firstAttribute="top" secondItem="ei7-8i-vp4" secondAttribute="bottom" id="CIq-u8-sbE"/>
|
|
||||||
<constraint firstItem="ei7-8i-vp4" firstAttribute="top" secondItem="znh-se-r1k" secondAttribute="bottom" id="G4H-cR-m9r"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="ei7-8i-vp4" secondAttribute="trailing" id="hCK-vS-hrZ"/>
|
|
||||||
<constraint firstItem="ei7-8i-vp4" firstAttribute="leading" secondItem="G47-Se-1Pm" secondAttribute="leading" id="hch-Ju-3QH"/>
|
|
||||||
<constraint firstItem="Ruy-Se-i9x" firstAttribute="top" secondItem="mbb-4g-X9Q" secondAttribute="bottom" id="zdn-6E-6mg"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
<tabBarItem key="tabBarItem" title="Interface Builder" image="photo" catalog="system" id="q1l-pB-Q4T"/>
|
|
||||||
<connections>
|
|
||||||
<outlet property="gradientActivityIndicatorView" destination="Yze-Dk-6Ol" id="v8n-H3-yyR"/>
|
|
||||||
</connections>
|
|
||||||
</viewController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="4Yh-MD-f7b" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="537" y="940"/>
|
|
||||||
</scene>
|
|
||||||
<!--Advanced Example-->
|
|
||||||
<scene sceneID="7wr-48-QKh">
|
|
||||||
<objects>
|
|
||||||
<viewController id="l28-cg-vu8" customClass="AdvancedExampleViewController" customModule="GradientLoadingBar_Example" customModuleProvider="target" sceneMemberID="viewController">
|
|
||||||
<layoutGuides>
|
|
||||||
<viewControllerLayoutGuide type="top" id="vvP-wW-eUz"/>
|
|
||||||
<viewControllerLayoutGuide type="bottom" id="9Nv-iX-Uwf"/>
|
|
||||||
</layoutGuides>
|
|
||||||
<view key="view" contentMode="scaleToFill" id="EZo-KN-FDG">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
|
||||||
<subviews>
|
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qZq-9D-wpV" customClass="LargeNavigationBarHeaderView" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="138"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="138" id="St1-Xs-Hvy"/>
|
|
||||||
</constraints>
|
|
||||||
<userDefinedRuntimeAttributes>
|
|
||||||
<userDefinedRuntimeAttribute type="string" keyPath="title" value="🚀 Advanced Example"/>
|
|
||||||
<userDefinedRuntimeAttribute type="string" keyPath="prompt" value="– Gradient Loading Bar –"/>
|
|
||||||
</userDefinedRuntimeAttributes>
|
|
||||||
</view>
|
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sJk-2n-yVK" userLabel="Wrapper View">
|
|
||||||
<rect key="frame" x="0.0" y="138" width="375" height="480"/>
|
|
||||||
<subviews>
|
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacing="12" translatesAutoresizingMaskIntoConstraints="NO" id="wzq-Eo-AIU">
|
|
||||||
<rect key="frame" x="16" y="194" width="343" height="92"/>
|
|
||||||
<subviews>
|
|
||||||
<button opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JKA-wf-nyj" userLabel="Toggle Programatically Button" customClass="BlueBorderedButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="sKv-MC-7kH"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Custom Superview"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleProgrammaticallyButtonTouchUpInside:" destination="l28-cg-vu8" eventType="touchUpInside" id="tZg-ye-xbi"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
<button opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Cbn-y8-lGV" userLabel="Toggle Custom Colors Button" customClass="BlueBorderedButton" customModule="GradientLoadingBar_Example" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="52" width="343" height="40"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="height" constant="40" id="emu-Jk-zMc"/>
|
|
||||||
</constraints>
|
|
||||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
|
|
||||||
<state key="normal" title="Custom Colors"/>
|
|
||||||
<connections>
|
|
||||||
<action selector="toggleCustomColorsButtonTouchUpInside:" destination="l28-cg-vu8" eventType="touchUpInside" id="BZJ-7f-xNF"/>
|
|
||||||
</connections>
|
|
||||||
</button>
|
|
||||||
</subviews>
|
|
||||||
</stackView>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="wzq-Eo-AIU" firstAttribute="leading" secondItem="sJk-2n-yVK" secondAttribute="leading" constant="16" id="139-Ak-t87"/>
|
|
||||||
<constraint firstItem="wzq-Eo-AIU" firstAttribute="centerY" secondItem="sJk-2n-yVK" secondAttribute="centerY" id="IqE-ns-FHJ"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="wzq-Eo-AIU" secondAttribute="trailing" constant="16" id="fcI-El-SAu"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
</subviews>
|
|
||||||
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstItem="sJk-2n-yVK" firstAttribute="top" secondItem="qZq-9D-wpV" secondAttribute="bottom" id="12P-NY-VtG"/>
|
|
||||||
<constraint firstItem="9Nv-iX-Uwf" firstAttribute="top" secondItem="sJk-2n-yVK" secondAttribute="bottom" id="3ZY-rc-hVf"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="sJk-2n-yVK" secondAttribute="trailing" id="Hqt-KP-eSH"/>
|
|
||||||
<constraint firstItem="qZq-9D-wpV" firstAttribute="top" secondItem="vvP-wW-eUz" secondAttribute="bottom" id="IEz-PS-kaF"/>
|
|
||||||
<constraint firstItem="sJk-2n-yVK" firstAttribute="leading" secondItem="EZo-KN-FDG" secondAttribute="leading" id="ZPm-e3-QdJ"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="qZq-9D-wpV" secondAttribute="trailing" id="kc6-MY-EP0"/>
|
|
||||||
<constraint firstItem="qZq-9D-wpV" firstAttribute="leading" secondItem="EZo-KN-FDG" secondAttribute="leading" id="piA-rQ-qe8"/>
|
|
||||||
</constraints>
|
|
||||||
</view>
|
|
||||||
<tabBarItem key="tabBarItem" title="Advanced Example" image="paperplane.fill" catalog="system" id="SyM-zw-tlg"/>
|
|
||||||
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
|
|
||||||
<connections>
|
|
||||||
<outlet property="customColorsButton" destination="Cbn-y8-lGV" id="1ag-73-Mlh"/>
|
|
||||||
<outlet property="programmaticallyButton" destination="JKA-wf-nyj" id="JRN-do-tSa"/>
|
|
||||||
</connections>
|
|
||||||
</viewController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="fkr-pn-u5J" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="1273" y="940"/>
|
|
||||||
</scene>
|
|
||||||
<!--Tab Bar Controller-->
|
|
||||||
<scene sceneID="gvY-1y-2Wb">
|
|
||||||
<objects>
|
|
||||||
<tabBarController automaticallyAdjustsScrollViewInsets="NO" id="31S-8b-Niv" sceneMemberID="viewController">
|
|
||||||
<toolbarItems/>
|
|
||||||
<tabBar key="tabBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="BSb-DD-oqz">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
</tabBar>
|
|
||||||
<connections>
|
|
||||||
<segue destination="vXZ-lx-hvc" kind="relationship" relationship="viewControllers" id="tyB-Oz-z6h"/>
|
|
||||||
<segue destination="G0W-Ta-C7r" kind="relationship" relationship="viewControllers" id="hPt-bW-PMO"/>
|
|
||||||
<segue destination="kcg-0d-bdI" kind="relationship" relationship="viewControllers" id="aD4-eR-rCH"/>
|
|
||||||
<segue destination="l28-cg-vu8" kind="relationship" relationship="viewControllers" id="4rs-U7-89R"/>
|
|
||||||
<segue destination="uZS-9p-82D" kind="relationship" relationship="viewControllers" id="Dcr-pe-ish"/>
|
|
||||||
</connections>
|
|
||||||
</tabBarController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="AXu-pz-Fer" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="538" y="134"/>
|
|
||||||
</scene>
|
|
||||||
<!--Nav Bar Example-->
|
|
||||||
<scene sceneID="HFG-Xb-221">
|
|
||||||
<objects>
|
|
||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="uZS-9p-82D" sceneMemberID="viewController">
|
|
||||||
<tabBarItem key="tabBarItem" title="Nav Bar Example" image="lightbulb.fill" catalog="system" id="r91-al-1Rr"/>
|
|
||||||
<toolbarItems/>
|
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="zAv-AH-IlN">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
</navigationBar>
|
|
||||||
<nil name="viewControllers"/>
|
|
||||||
<connections>
|
|
||||||
<segue destination="sEh-Tc-lv1" kind="relationship" relationship="rootViewController" id="Jor-5M-Hj4"/>
|
|
||||||
</connections>
|
|
||||||
</navigationController>
|
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="yG3-j8-Ejc" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
|
||||||
</objects>
|
|
||||||
<point key="canvasLocation" x="2009" y="940"/>
|
|
||||||
</scene>
|
|
||||||
</scenes>
|
|
||||||
<resources>
|
|
||||||
<image name="house" catalog="system" width="128" height="110"/>
|
|
||||||
<image name="lightbulb.fill" catalog="system" width="113" height="128"/>
|
|
||||||
<image name="paperplane.fill" catalog="system" width="128" height="121"/>
|
|
||||||
<image name="photo" catalog="system" width="128" height="93"/>
|
|
||||||
<image name="rectangle.and.arrow.up.right.and.arrow.down.left" catalog="system" width="121" height="128"/>
|
|
||||||
</resources>
|
|
||||||
</document>
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
//
|
|
||||||
// BasicExampleViewController.swift
|
|
||||||
// GradientLoadingBar_Example
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 30.09.17.
|
|
||||||
// Copyright © 2017 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import GradientLoadingBar
|
|
||||||
|
|
||||||
class BasicExampleViewController: UIViewController {
|
|
||||||
// MARK: - Public methods
|
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
|
||||||
super.viewWillDisappear(animated)
|
|
||||||
|
|
||||||
GradientLoadingBar.shared.fadeOut()
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction private func showButtonTouchUpInside(_: Any) {
|
|
||||||
GradientLoadingBar.shared.fadeIn()
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction private func hideButtonTouchUpInside(_: Any) {
|
|
||||||
GradientLoadingBar.shared.fadeOut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//
|
|
||||||
// UIColor+CustomColors.swift
|
|
||||||
// GradientLoadingBar_Example
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 29.08.18.
|
|
||||||
// Copyright © 2018 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
extension UIColor {
|
|
||||||
/// Config that contains all our custom defined colors.
|
|
||||||
enum CustomColors {
|
|
||||||
static let blue = #colorLiteral(red: 0.2862745098, green: 0.5647058824, blue: 0.8862745098, alpha: 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>en</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>$(PRODUCT_NAME)</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>????</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>1</string>
|
|
||||||
<key>LSRequiresIPhoneOS</key>
|
|
||||||
<true/>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
|
||||||
<string>LaunchScreen</string>
|
|
||||||
<key>UIMainStoryboardFile</key>
|
|
||||||
<string>Main</string>
|
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
|
||||||
<array>
|
|
||||||
<string>armv7</string>
|
|
||||||
</array>
|
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
//
|
|
||||||
// InterfaceBuilderExampleViewController.swift
|
|
||||||
// GradientLoadingBar_Example
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 12.10.19.
|
|
||||||
// Copyright © 2018 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import GradientLoadingBar
|
|
||||||
|
|
||||||
class InterfaceBuilderExampleViewController: UIViewController {
|
|
||||||
// MARK: - Outlets
|
|
||||||
|
|
||||||
@IBOutlet private var gradientActivityIndicatorView: GradientActivityIndicatorView!
|
|
||||||
|
|
||||||
// MARK: - Public methods
|
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
|
||||||
super.viewWillDisappear(animated)
|
|
||||||
|
|
||||||
gradientActivityIndicatorView.fadeOut()
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction private func showButtonTouchUpInside(_: Any) {
|
|
||||||
gradientActivityIndicatorView.fadeIn()
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction private func hideButtonTouchUpInside(_: Any) {
|
|
||||||
gradientActivityIndicatorView.fadeOut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
//
|
|
||||||
// NavigationBarExampleViewController.swift
|
|
||||||
// GradientLoadingBar_Example
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 29.08.18.
|
|
||||||
// Copyright © 2018 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import GradientLoadingBar
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
setupGradientProgressIndicatorView()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
|
||||||
super.viewWillDisappear(animated)
|
|
||||||
|
|
||||||
gradientProgressIndicatorView.fadeOut()
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction private func showButtonTouchUpInside(_: Any) {
|
|
||||||
gradientProgressIndicatorView.fadeIn()
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBAction private func hideButtonTouchUpInside(_: Any) {
|
|
||||||
gradientProgressIndicatorView.fadeOut()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private methods
|
|
||||||
|
|
||||||
private func setupGradientProgressIndicatorView() {
|
|
||||||
guard let navigationBar = navigationController?.navigationBar else { return }
|
|
||||||
|
|
||||||
gradientProgressIndicatorView.fadeOut(duration: 0)
|
|
||||||
|
|
||||||
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),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
//
|
|
||||||
// SafeAreaExampleViewController.swift
|
|
||||||
// GradientLoadingBar_Example
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 30.08.18.
|
|
||||||
// Copyright © 2018 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import GradientLoadingBar
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
gradientLoadingBar.fadeOut()
|
|
||||||
notchGradientLoadingBar.fadeOut()
|
|
||||||
}
|
|
||||||
|
|
||||||
@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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
//
|
|
||||||
// BlueBorderedButton.swift
|
|
||||||
// GradientLoadingBar_Example
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 29.08.18.
|
|
||||||
// Copyright © 2018 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
@IBDesignable
|
|
||||||
final class BlueBorderedButton: UIButton {
|
|
||||||
// MARK: - Initializer
|
|
||||||
|
|
||||||
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 = UIColor.CustomColors.blue
|
|
||||||
|
|
||||||
layer.cornerRadius = 4
|
|
||||||
layer.borderColor = UIColor.CustomColors.blue.cgColor
|
|
||||||
layer.borderWidth = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
//
|
|
||||||
// BlueFilledButton.swift
|
|
||||||
// GradientLoadingBar_Example
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 29.08.18.
|
|
||||||
// Copyright © 2018 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
@IBDesignable
|
|
||||||
final class BlueFilledButton: UIButton {
|
|
||||||
// MARK: - Initializer
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: frame)
|
|
||||||
|
|
||||||
commonInit()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
super.init(coder: aDecoder)
|
|
||||||
|
|
||||||
commonInit()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func prepareForInterfaceBuilder() {
|
|
||||||
super.prepareForInterfaceBuilder()
|
|
||||||
|
|
||||||
commonInit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private methods
|
|
||||||
|
|
||||||
private func commonInit() {
|
|
||||||
tintColor = .white
|
|
||||||
backgroundColor = UIColor.CustomColors.blue
|
|
||||||
|
|
||||||
layer.cornerRadius = 4.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
//
|
|
||||||
// LargeNavigationBarHeaderView.swift
|
|
||||||
// GradientLoadingBar_Example
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 23.09.19.
|
|
||||||
// Copyright © 2019 CocoaPods. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
@IBDesignable
|
|
||||||
final class LargeNavigationBarHeaderView: UIView {
|
|
||||||
// MARK: - Public properties
|
|
||||||
|
|
||||||
@IBInspectable var title: String = "" {
|
|
||||||
didSet {
|
|
||||||
titleLabel.text = title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IBInspectable var prompt: String = "" {
|
|
||||||
didSet {
|
|
||||||
promptLabel.text = prompt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private properties
|
|
||||||
|
|
||||||
private let titleLabel = UILabel(frame: .zero)
|
|
||||||
|
|
||||||
private let promptLabel = UILabel(frame: .zero)
|
|
||||||
|
|
||||||
// MARK: - Initializer
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: frame)
|
|
||||||
|
|
||||||
commonInit()
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
super.init(coder: coder)
|
|
||||||
|
|
||||||
commonInit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private methods
|
|
||||||
|
|
||||||
private func commonInit() {
|
|
||||||
setupTitleLabel()
|
|
||||||
setupPromptLabel()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupTitleLabel() {
|
|
||||||
let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .largeTitle)
|
|
||||||
titleLabel.font = UIFont.systemFont(ofSize: fontDescriptor.pointSize, weight: .bold)
|
|
||||||
titleLabel.textColor = .label
|
|
||||||
|
|
||||||
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
addSubview(titleLabel)
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16.0),
|
|
||||||
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16.0),
|
|
||||||
|
|
||||||
titleLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupPromptLabel() {
|
|
||||||
promptLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
|
|
||||||
promptLabel.textAlignment = .center
|
|
||||||
promptLabel.textColor = .label
|
|
||||||
|
|
||||||
promptLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
addSubview(promptLabel)
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
promptLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
|
|
||||||
promptLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
|
|
||||||
|
|
||||||
promptLabel.topAnchor.constraint(equalTo: topAnchor, constant: 9.5),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 = "<group>";
|
||||||
|
};
|
||||||
|
/* 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 = "<group>";
|
||||||
|
};
|
||||||
|
F54507862F12839600697C4F /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
F54507852F12839600697C4F /* GradientLoadingBarExample.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* 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 */;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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: <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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
}
|
||||||
@@ -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: <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),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,49 +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 'GradientLoadingBar_Example' do
|
|
||||||
pod 'GradientLoadingBar', :path => '../'
|
|
||||||
|
|
||||||
# Development pods.
|
|
||||||
pod 'SwiftFormat/CLI', '~> 0.41'
|
|
||||||
pod 'SwiftLint', '~> 0.42'
|
|
||||||
pod 'SwiftConfigurationFiles', :git => 'https://github.com/fxm90/SwiftConfigurationFiles.git'
|
|
||||||
|
|
||||||
target 'GradientLoadingBar_Tests' do
|
|
||||||
inherit! :search_paths
|
|
||||||
|
|
||||||
# Pods for testing.
|
|
||||||
end
|
|
||||||
|
|
||||||
target 'GradientLoadingBar_SnapshotTests' do
|
|
||||||
inherit! :search_paths
|
|
||||||
|
|
||||||
# Pods for snapshot testing.
|
|
||||||
pod 'SnapshotTesting', '~> 1.7', :inhibit_warnings => true
|
|
||||||
end
|
|
||||||
|
|
||||||
post_install do |installer|
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# We support all iOS versions starting from 9.0. Therefore we manually decrease the deployment target for `GradientLoadingBar` here,
|
|
||||||
# so other (development / testing related pods) can use a higher deployment target, which is defined using `platform :ios` above.
|
|
||||||
installer.pods_project.targets.each do |target|
|
|
||||||
next unless target.name == 'GradientLoadingBar'
|
|
||||||
|
|
||||||
target.build_configurations.each do |config|
|
|
||||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
PODS:
|
|
||||||
- GradientLoadingBar (2.2.3):
|
|
||||||
- LightweightObservable (~> 2.1)
|
|
||||||
- LightweightObservable (2.1.2)
|
|
||||||
- SnapshotTesting (1.9.0)
|
|
||||||
- SwiftConfigurationFiles (0.0.1)
|
|
||||||
- SwiftFormat/CLI (0.49.1)
|
|
||||||
- SwiftLint (0.45.1)
|
|
||||||
|
|
||||||
DEPENDENCIES:
|
|
||||||
- GradientLoadingBar (from `../`)
|
|
||||||
- SnapshotTesting (~> 1.7)
|
|
||||||
- SwiftConfigurationFiles (from `https://github.com/fxm90/SwiftConfigurationFiles.git`)
|
|
||||||
- SwiftFormat/CLI (~> 0.41)
|
|
||||||
- SwiftLint (~> 0.42)
|
|
||||||
|
|
||||||
SPEC REPOS:
|
|
||||||
https://github.com/CocoaPods/Specs.git:
|
|
||||||
- LightweightObservable
|
|
||||||
- SnapshotTesting
|
|
||||||
- SwiftFormat
|
|
||||||
- SwiftLint
|
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
|
||||||
GradientLoadingBar:
|
|
||||||
:path: "../"
|
|
||||||
SwiftConfigurationFiles:
|
|
||||||
:git: https://github.com/fxm90/SwiftConfigurationFiles.git
|
|
||||||
|
|
||||||
CHECKOUT OPTIONS:
|
|
||||||
SwiftConfigurationFiles:
|
|
||||||
:commit: 5e499075b42f5c986a6ad5a274cf8efa743337b5
|
|
||||||
:git: https://github.com/fxm90/SwiftConfigurationFiles.git
|
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
|
||||||
GradientLoadingBar: 157bd321c74e6bd0b0d87de735336060011393ab
|
|
||||||
LightweightObservable: c5ac85423a5edbed9a920b4d5c7b8f94ab3688c4
|
|
||||||
SnapshotTesting: 6141c48b6aa76ead61431ca665c14ab9a066c53b
|
|
||||||
SwiftConfigurationFiles: 1cf2228a911ebed9f42f8dec077bb634f04ca6c8
|
|
||||||
SwiftFormat: 16b41f3229f5e7edb130ac4cd631cceed7af7d5e
|
|
||||||
SwiftLint: 06ac37e4d38c7068e0935bb30cda95f093bec761
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: 36b90780cb508f9eaca4f0ac59660e3e748de601
|
|
||||||
|
|
||||||
COCOAPODS: 1.11.2
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
Copyright (c) 2019-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.
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
//
|
|
||||||
// Disposable.swift
|
|
||||||
// LightweightObservable
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 11.02.19.
|
|
||||||
// Copyright © 2019 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
/// Helper to allow storing multiple disposables and matching name from [RxSwift](https://github.com/ReactiveX/RxSwift).
|
|
||||||
public typealias DisposeBag = [Disposable]
|
|
||||||
|
|
||||||
/// Executes a given closure on it's deallocation.
|
|
||||||
public final class Disposable {
|
|
||||||
// MARK: - Types
|
|
||||||
|
|
||||||
/// Type for closure to be executed on deallocation.
|
|
||||||
public typealias Dispose = () -> Void
|
|
||||||
|
|
||||||
// MARK: - Private properties
|
|
||||||
|
|
||||||
/// Closure to be executed on deallocation.
|
|
||||||
private let dispose: Dispose
|
|
||||||
|
|
||||||
// MARK: - Initializer
|
|
||||||
|
|
||||||
/// Creates a new instance.
|
|
||||||
///
|
|
||||||
/// - Parameter dispose: The closure that is executed on deallocation.
|
|
||||||
public init(_ dispose: @escaping Dispose) {
|
|
||||||
self.dispose = dispose
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Executes our closure.
|
|
||||||
deinit {
|
|
||||||
dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Public methods
|
|
||||||
|
|
||||||
/// Adds the current instance to the given array of disposables.
|
|
||||||
public func disposed(by bag: inout DisposeBag) {
|
|
||||||
bag.append(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
//
|
|
||||||
// Observable+Equatable.swift
|
|
||||||
// LightweightObservable
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 01.05.19.
|
|
||||||
// Copyright © 2019 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
/// Additional helper methods for an observable that that underlying type conforms to `Equatable`.
|
|
||||||
public extension Observable where T: Equatable {
|
|
||||||
// MARK: - Types
|
|
||||||
|
|
||||||
/// The type for the filter closure.
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - value: The current (new) value.
|
|
||||||
/// - oldValue: The previous (old) value.
|
|
||||||
///
|
|
||||||
/// - Returns: `true` if the filter matches, otherwise `false`.
|
|
||||||
typealias Filter = (_ value: Value, _ oldValue: OldValue) -> Bool
|
|
||||||
|
|
||||||
// MARK: - Public methods
|
|
||||||
|
|
||||||
/// Informs the given observer on changes to our `value`, only if the given filter matches.
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - filter: The closure, that must return `true` in order for the observer to be notified.
|
|
||||||
/// - observer: The closure that is notified on changes if the filter succeeds.
|
|
||||||
///
|
|
||||||
/// - Note:
|
|
||||||
/// The `oldValue` of the observer always contains the previous value that passed the filter! E.g. if you filter for even numbers:
|
|
||||||
/// ```
|
|
||||||
/// | Emitted values: | 0 | 1 | 2 | 3 | 4 | 5 |
|
|
||||||
/// | -------------------------- | -------- | -------- | -------- | -------- | -------- | -------- |
|
|
||||||
/// | filter(value, oldValue): | (0, nil) | (1, 0) | (2, 1) | (3, 2) | (4, 3) | (5, 4) |
|
|
||||||
/// | result: | true | false | true | false | true | false |
|
|
||||||
/// | observer(value, oldValue): | (0, nil) | | (2, 0) | | (4, 2) | |
|
|
||||||
/// ```
|
|
||||||
func subscribe(filter: @escaping Filter, observer: @escaping Observer) -> Disposable {
|
|
||||||
// As we're only calling the observer after the given `filter` succeeds, we need to save the last `newValue` that passed the filter.
|
|
||||||
// Then we can pass this value as the correct `oldValue` on the next call to the given `observer`.
|
|
||||||
var filteredOldValue: OldValue = nil
|
|
||||||
|
|
||||||
return subscribe { newValue, oldValue in
|
|
||||||
// For having a correct working filter we need to pass in the current `oldValue`.
|
|
||||||
if filter(newValue, oldValue) {
|
|
||||||
observer(newValue, filteredOldValue)
|
|
||||||
filteredOldValue = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Informs the given observer on **distinct** changes to our `value`.
|
|
||||||
///
|
|
||||||
/// - Parameter observer: The closure that is notified on changes.
|
|
||||||
func subscribeDistinct(_ observer: @escaping Observer) -> Disposable {
|
|
||||||
subscribe(filter: { $0 != $1 },
|
|
||||||
observer: observer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
//
|
|
||||||
// Observable+KeyPath.swift
|
|
||||||
// LightweightObservable
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 03.01.20.
|
|
||||||
// Copyright © 2020 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
/// Additional helper methods for binding an observable to a property using Swifts `KeyPath` feature.
|
|
||||||
public extension Observable {
|
|
||||||
/// Updates the property at the given key-path on changes to our property `value`.
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - keyPath: The key-path that indicates the property to assign.
|
|
||||||
/// - object: The object containing the property to update.
|
|
||||||
func bind<Root: AnyObject>(to keyPath: ReferenceWritableKeyPath<Root, Value>, on object: Root) -> Disposable {
|
|
||||||
subscribe { newValue, _ in
|
|
||||||
object[keyPath: keyPath] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the property at the given key-path on changes to our property `value`.
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - keyPath: The key-path that indicates the property to assign.
|
|
||||||
/// - object: The object containing the property to update.
|
|
||||||
///
|
|
||||||
/// - Note: We need to explicitly define this method for an optional type of `Value`, as otherwise we e.g. could not bind a `String` to the
|
|
||||||
/// optional string property `text` of an `UILabel`
|
|
||||||
func bind<Root>(to keyPath: ReferenceWritableKeyPath<Root, Value?>, on object: Root) -> Disposable {
|
|
||||||
subscribe { newValue, _ in
|
|
||||||
object[keyPath: keyPath] = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
//
|
|
||||||
// Observable.swift
|
|
||||||
// LightweightObservable
|
|
||||||
//
|
|
||||||
// Created by Felix Mau on 11.02.19.
|
|
||||||
// Copyright © 2019 Felix Mau. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
/// A lightweight implementation of an observable sequence that you can subscribe to.
|
|
||||||
///
|
|
||||||
/// - Note: Partly based on [roberthein/Observable](https://github.com/roberthein/Observable).
|
|
||||||
///
|
|
||||||
/// - Remark: I'd prefer having a protocol definition here, but casting an instance with a generic (e.g. `Variable<Int>(0)`) to a protocol
|
|
||||||
/// with an associated type (`Observable<Int>`) doesn't work yet. Therefore we use an "abstract" class as a workaround.
|
|
||||||
public class Observable<T> {
|
|
||||||
// MARK: - Types
|
|
||||||
|
|
||||||
/// The type for the new value of the observable.
|
|
||||||
public typealias Value = T
|
|
||||||
|
|
||||||
/// The type for the previous value of the observable.
|
|
||||||
public typealias OldValue = T?
|
|
||||||
|
|
||||||
/// The type for the closure to executed on change of the observable.
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - value: The current (new) value.
|
|
||||||
/// - oldValue: The previous (old) value.
|
|
||||||
public typealias Observer = (_ value: Value, _ oldValue: OldValue) -> Void
|
|
||||||
|
|
||||||
/// We store all observers within a dictionary, for which this is the type of the key.
|
|
||||||
private typealias Index = UInt
|
|
||||||
|
|
||||||
// MARK: - Public properties
|
|
||||||
|
|
||||||
/// The current (readonly) value of the observable (if available).
|
|
||||||
///
|
|
||||||
/// - Note: We're using a computed property here, cause we need to override this property without nullability in the subclass `Variable`.
|
|
||||||
///
|
|
||||||
/// - Attention: It's always better to subscribe to a given observable! This **shortcut** should only be used during **testing**.
|
|
||||||
public var value: Value? {
|
|
||||||
fatalError("⚠️ – Trying to access an abstract property! Subclasses need to overwrite and implement this computed property.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private properties
|
|
||||||
|
|
||||||
/// The index of the last inserted observer.
|
|
||||||
private var lastIndex: Index = 0
|
|
||||||
|
|
||||||
/// Map with all **active** observers.
|
|
||||||
private var observers = [Index: Observer]()
|
|
||||||
|
|
||||||
// MARK: - Initalizer
|
|
||||||
|
|
||||||
/// Initializes a new observable.
|
|
||||||
///
|
|
||||||
/// - Note: Declared `fileprivate` in order to prevent directly initializing an observable, that can't emit values.
|
|
||||||
fileprivate init() {}
|
|
||||||
|
|
||||||
// MARK: - Public methods
|
|
||||||
|
|
||||||
/// Informs the given observer on changes to our underlying property `value`.
|
|
||||||
///
|
|
||||||
/// - Parameter observer: The closure that is notified on changes.
|
|
||||||
public func subscribe(_ observer: @escaping Observer) -> Disposable {
|
|
||||||
let indexForNewObserver = lastIndex + 1
|
|
||||||
observers[indexForNewObserver] = observer
|
|
||||||
lastIndex = indexForNewObserver
|
|
||||||
|
|
||||||
// Return a disposable, that removes the entry for this observer on it's deallocation.
|
|
||||||
return Disposable { [weak self] in
|
|
||||||
self?.observers[indexForNewObserver] = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private methods
|
|
||||||
|
|
||||||
fileprivate func notifyObserver(with value: Value, from oldValue: OldValue) {
|
|
||||||
observers.values.forEach { observer in
|
|
||||||
observer(value, oldValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Starts empty and only emits new elements to subscribers.
|
|
||||||
public final class PublishSubject<T>: Observable<T> {
|
|
||||||
// MARK: - Public properties
|
|
||||||
|
|
||||||
/// The current (readonly) value of the observable (if available).
|
|
||||||
override public var value: Value? {
|
|
||||||
_value
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private properties
|
|
||||||
|
|
||||||
/// The storage for our computed property.
|
|
||||||
///
|
|
||||||
/// - Note: Workaround for compiler error `Cannot override with a stored property 'value'`.
|
|
||||||
private var _value: Value?
|
|
||||||
|
|
||||||
// MARK: - Initializer
|
|
||||||
|
|
||||||
/// Initializes a new publish subject.
|
|
||||||
///
|
|
||||||
/// - Note: As we've made the initializer to the super class `Observable` fileprivate, we must override it here to allow public access.
|
|
||||||
override public init() {
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Public methods
|
|
||||||
|
|
||||||
/// Updates the publish subject using the given value.
|
|
||||||
public func update(_ value: Value) {
|
|
||||||
let oldValue = _value
|
|
||||||
_value = value
|
|
||||||
|
|
||||||
// We inform the observer here instead of using `didSet` on `_value` to prevent unwrapping an optional (`_value` is nullable, as we're starting empty).
|
|
||||||
// Unwrapping lead to issues / crashes on having an underlying optional type, e.g. `PublishSubject<Int?>`.
|
|
||||||
notifyObserver(with: value, from: oldValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Starts with an initial value and replays it or the latest element to new subscribers.
|
|
||||||
public final class Variable<T>: Observable<T> {
|
|
||||||
// MARK: - Public properties
|
|
||||||
|
|
||||||
/// The current (read- and writeable) value of the variable.
|
|
||||||
override public var value: Value {
|
|
||||||
get { _value }
|
|
||||||
set { _value = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private properties
|
|
||||||
|
|
||||||
/// The storage for our computed property.
|
|
||||||
///
|
|
||||||
/// - Note: Workaround for compiler error `Cannot override with a stored property 'value'`.
|
|
||||||
private var _value: Value {
|
|
||||||
didSet {
|
|
||||||
notifyObserver(with: value, from: oldValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Initializer
|
|
||||||
|
|
||||||
/// Initializes a new variable with the given value.
|
|
||||||
///
|
|
||||||
/// - Note: We keep the initializer to the super class `Observable` fileprivate in order to verify always having a value.
|
|
||||||
public init(_ value: Value) {
|
|
||||||
_value = value
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Public methods
|
|
||||||
|
|
||||||
override public func subscribe(_ observer: @escaping Observer) -> Disposable {
|
|
||||||
let disposable = super.subscribe(observer)
|
|
||||||
|
|
||||||
// A variable should inform the observer with the initial value.
|
|
||||||
observer(_value, nil)
|
|
||||||
|
|
||||||
return disposable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,294 +0,0 @@
|
|||||||
![Header][header]
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img src="https://img.shields.io/badge/Swift-5.0-green.svg?style=flat" alt="Swift Version" />
|
|
||||||
<img src="http://img.shields.io/travis/com/fxm90/LightweightObservable.svg?style=flat" alt="CI Status" />
|
|
||||||
<img src="https://img.shields.io/codecov/c/github/fxm90/LightweightObservable.svg?style=flat" alt="Code Coverage" />
|
|
||||||
<img src="https://img.shields.io/cocoapods/v/LightweightObservable.svg?style=flat" alt="Version" />
|
|
||||||
<img src="https://img.shields.io/cocoapods/l/LightweightObservable.svg?style=flat" alt="License" />
|
|
||||||
<img src="https://img.shields.io/cocoapods/p/LightweightObservable.svg?style=flat" alt="Platform" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
Lightweight Observable is a simple implementation of an observable sequence that you can subscribe to. The framework is designed to be minimal meanwhile convenient. The entire code is only ~100 lines (excluding comments). With Lightweight Observable you can easily set up UI-Bindings in an MVVM application, handle asynchronous network calls and a lot more.
|
|
||||||
|
|
||||||
##### Credits
|
|
||||||
The code was heavily influenced by [roberthein/observable](https://github.com/roberthein/Observable). However I needed something that was syntactically closer to [RxSwift](https://github.com/ReactiveX/RxSwift), which is why I came up with this code, and for re-usability reasons afterwards moved it into a CocoaPod.
|
|
||||||
|
|
||||||
##### Migration Guide
|
|
||||||
If you want to update from version 1.x.x, please have a look at the [Lightweight Observable 2.0 Migration Guide
|
|
||||||
](Documentation/Lightweight%20Observable%202.0%20Migration%20Guide.md)
|
|
||||||
|
|
||||||
### Example
|
|
||||||
To run the example project, clone the repo, and open the workspace from the Example directory.
|
|
||||||
|
|
||||||
### Requirements
|
|
||||||
- Swift 5.0
|
|
||||||
- Xcode 10.2+
|
|
||||||
- iOS 9.0+
|
|
||||||
|
|
||||||
### Integration
|
|
||||||
##### CocoaPods
|
|
||||||
[CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate Lightweight Observable into your Xcode project using CocoaPods, specify it in your `Podfile`:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
pod 'LightweightObservable', '~> 2.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 Lightweight Observable into your Xcode project using Carthage, specify it in your `Cartfile`:
|
|
||||||
|
|
||||||
```ogdl
|
|
||||||
github "fxm90/LightweightObservable" ~> 2.0
|
|
||||||
```
|
|
||||||
Run carthage update to build the framework and drag the built `LightweightObservable.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 Lightweight Observable does support its use on supported platforms.
|
|
||||||
|
|
||||||
Once you have your Swift package set up, adding Lightweight Observable 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/LightweightObservable", from: "2.0.0")
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### How to use
|
|
||||||
The framework provides three classes `Observable`, `PublishSubject` and `Variable`:
|
|
||||||
|
|
||||||
- `Observable`: An observable sequence that you can subscribe to, but not change the underlying value (immutable). This is useful to avoid side-effects on an internal API.
|
|
||||||
- `PublishSubject`: Subclass of `Observable` that starts empty and only emits new elements to subscribers (mutable).
|
|
||||||
- `Variable`: Subclass of `Observable` that starts with an initial value and replays it or the latest element to new subscribers (mutable).
|
|
||||||
|
|
||||||
#### – Create and update a `PublishSubject`
|
|
||||||
A `PublishSubject` starts empty and only emits new elements to subscribers.
|
|
||||||
|
|
||||||
```swift
|
|
||||||
let userLocationSubject = PublishSubject<CLLocation>()
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
userLocationSubject.update(receivedUserLocation)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### – Create and update a `Variable`
|
|
||||||
A `Variable` starts with an initial value and replays it or the latest element to new subscribers.
|
|
||||||
|
|
||||||
```swift
|
|
||||||
let formattedTimeSubject = Variable("4:20 PM")
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
formattedTimeSubject.value = "4:21 PM"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### – Create an `Observable`
|
|
||||||
Initializing an observable directly is not possible, as this would lead to a sequence that will never change. Instead you need to cast a `PublishSubject` or a `Variable` to an Observable.
|
|
||||||
|
|
||||||
```swift
|
|
||||||
var formattedTime: Observable<String> {
|
|
||||||
formattedTimeSubject
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```swift
|
|
||||||
lazy var formattedTime: Observable<String> = formattedTimeSubject
|
|
||||||
```
|
|
||||||
|
|
||||||
#### – Subscribe to changes
|
|
||||||
A subscriber will be informed at different times, depending on the corresponding subclass of the observable:
|
|
||||||
|
|
||||||
- `PublishSubject`: Starts empty and only emits new elements to subscribers.
|
|
||||||
- `Variable`: Starts with an initial value and replays it or the latest element to new subscribers.
|
|
||||||
|
|
||||||
##### – Closure based subscription
|
|
||||||
**Declaration**
|
|
||||||
|
|
||||||
```swift
|
|
||||||
func subscribe(_ observer: @escaping Observer) -> Disposable
|
|
||||||
```
|
|
||||||
|
|
||||||
Use this method to subscribe to an observable via a closure:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
formattedTime.subscribe { [weak self] newFormattedTime, oldFormattedTime in
|
|
||||||
self?.timeLabel.text = newFormattedTime
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Please notice that the old value (`oldFormattedTime`) is an optional of the underlying type, as we might not have this value on the initial call to the subscriber.
|
|
||||||
|
|
||||||
**Important:** To avoid retain cycles and/or crashes, **always** use `[weak self]` when self is needed by an observer.
|
|
||||||
|
|
||||||
##### - KeyPath based subscription
|
|
||||||
**Declaration**
|
|
||||||
|
|
||||||
```swift
|
|
||||||
func bind<Root: AnyObject>(to keyPath: ReferenceWritableKeyPath<Root, Value>, on object: Root) -> Disposable
|
|
||||||
```
|
|
||||||
|
|
||||||
It is also possible to use Swift's KeyPath feature to bind an observable directly to a property:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
formattedTime.bind(to: \.text, on: timeLabel)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### – Memory Management (`Disposable` / `DisposeBag`)
|
|
||||||
|
|
||||||
When you subscribe to an `Observable` the method returns a `Disposable`, which is basically a reference to the new subscription.
|
|
||||||
|
|
||||||
We need to maintain it, in order to properly control the life-cycle of that subscription.
|
|
||||||
|
|
||||||
Let me explain you why in a little example:
|
|
||||||
|
|
||||||
> Imagine having a MVVM application using a service layer for network calls. A service is used as a singleton across the entire app.
|
|
||||||
>
|
|
||||||
> The view-model has a reference to a service and subscribes to an observable property of this service. The subscription-closure is now saved inside the observable property on the service.
|
|
||||||
>
|
|
||||||
> If the view-model gets deallocated (e.g. due to a dismissed view-controller), without noticing the observable property somehow, the subscription-closure would continue to be alive.
|
|
||||||
>
|
|
||||||
> As a workaround, we store the returned disposable from the subscription on the view-model. On deallocation of the disposable, it automatically informs the observable property to remove the referenced subscription closure.
|
|
||||||
|
|
||||||
In case you only use a single subscriber you can store the returned `Disposable` to a variable:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
// MARK: - Using `subscribe(_:)`
|
|
||||||
|
|
||||||
let disposable = formattedTime.subscribe { [weak self] newFormattedTime, oldFormattedTime in
|
|
||||||
self?.timeLabel.text = newFormattedTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Using a `bind(to:on:)`
|
|
||||||
|
|
||||||
let disposable = dateTimeViewModel
|
|
||||||
.formattedTime
|
|
||||||
.bind(to: \.text, on: timeLabel)
|
|
||||||
```
|
|
||||||
|
|
||||||
In case you're having multiple observers, you can store all returned `Disposable` in an array of `Disposable`. (To match the syntax from [RxSwift](https://github.com/ReactiveX/RxSwift), this pod contains a typealias called `DisposeBag`, which is an array of `Disposable`).
|
|
||||||
|
|
||||||
```swift
|
|
||||||
var disposeBag = DisposeBag()
|
|
||||||
|
|
||||||
// MARK: - Using `subscribe(_:)`
|
|
||||||
|
|
||||||
formattedTime.subscribe { [weak self] newFormattedTime, oldFormattedTime in
|
|
||||||
self?.timeLabel.text = newFormattedTime
|
|
||||||
}.disposed(by: &disposeBag)
|
|
||||||
|
|
||||||
formattedDate.subscribe { [weak self] newFormattedDate, oldFormattedDate in
|
|
||||||
self?.dateLabel.text = newFormattedDate
|
|
||||||
}.disposed(by: &disposeBag)
|
|
||||||
|
|
||||||
// MARK: - Using a `bind(to:on:)`
|
|
||||||
|
|
||||||
formattedTime
|
|
||||||
.bind(to: \.text, on: timeLabel)
|
|
||||||
.disposed(by: &disposeBag)
|
|
||||||
|
|
||||||
formattedDate
|
|
||||||
.bind(to: \.text, on: dateLabel)
|
|
||||||
.disposed(by: &disposeBag)
|
|
||||||
```
|
|
||||||
|
|
||||||
A `DisposeBag` is exactly what it says it is, a bag (or array) of disposables.
|
|
||||||
|
|
||||||
#### – Observing `Equatable` values
|
|
||||||
If you create an Observable which underlying type conforms to `Equatable` you can subscribe to changes using a specific filter. Therefore this pod contains the method:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
typealias Filter = (NewValue, OldValue) -> Bool
|
|
||||||
|
|
||||||
func subscribe(filter: @escaping Filter, observer: @escaping Observer) -> Disposable {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Using this method, the observer will only be notified on changes if the corresponding filter matches.
|
|
||||||
|
|
||||||
This pod comes with one predefined filter method, called `subscribeDistinct`. Subscribing to an observable using this method, will only notify the observer if the new value is different from the old value. This is useful to prevent unnecessary UI-Updates.
|
|
||||||
|
|
||||||
Feel free to add more filters, by extending the `Observable` like this:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
extension Observable where T: Equatable {}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### – Getting the current value synchronously
|
|
||||||
You can get the current value of the `Observable` by accessing the property `value`. However it is always better to subscribe to a given observable! This **shortcut** should only be used during **testing**.
|
|
||||||
|
|
||||||
```swift
|
|
||||||
XCTAssertEqual(viewModel.formattedTime.value, "4:20")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sample code
|
|
||||||
Using the given approach, your view-model could look like this:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
class TimeViewModel {
|
|
||||||
// MARK: - Public properties
|
|
||||||
|
|
||||||
/// The current time as a formatted string (**immutable**).
|
|
||||||
var formattedTime: Observable<String> {
|
|
||||||
formattedTimeSubject
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private properties
|
|
||||||
|
|
||||||
/// The current time as a formatted string (**mutable**).
|
|
||||||
private let formattedTimeSubject: Variable<String> = Variable("\(Date())")
|
|
||||||
|
|
||||||
private var timer: Timer?
|
|
||||||
|
|
||||||
// MARK: - Initializer
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// Update variable with current time every second.
|
|
||||||
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] _ in
|
|
||||||
self?.formattedTimeSubject.value = "\(Date())"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And your view controller like this:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
class TimeViewController: UIViewController {
|
|
||||||
// MARK: - Outlets
|
|
||||||
|
|
||||||
@IBOutlet private var timeLabel: UILabel!
|
|
||||||
|
|
||||||
// MARK: - Private properties
|
|
||||||
|
|
||||||
/// The view model calculating the current time.
|
|
||||||
private let timeViewModel = TimeViewModel()
|
|
||||||
|
|
||||||
/// The dispose bag for this view controller. On it's deallocation, it removes the
|
|
||||||
/// subscription-closures from the corresponding observable-properties.
|
|
||||||
private var disposeBag = DisposeBag()
|
|
||||||
|
|
||||||
// MARK: - Public methods
|
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
timeViewModel
|
|
||||||
.formattedTime
|
|
||||||
.bind(to: \.text, on: timeLabel)
|
|
||||||
.disposed(by: &disposeBag)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Feel free to check out the example application as well for a better understanding of this approach 🙂
|
|
||||||
|
|
||||||
|
|
||||||
### Author
|
|
||||||
Felix Mau (me(@)felix.hamburg)
|
|
||||||
|
|
||||||
|
|
||||||
### License
|
|
||||||
LightweightObservable is available under the MIT license. See the LICENSE file for more info.
|
|
||||||
|
|
||||||
|
|
||||||
[header]: Assets/header.png
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "GradientLoadingBar",
|
|
||||||
"version": "2.2.3",
|
|
||||||
"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": "2.2.3"
|
|
||||||
},
|
|
||||||
"swift_versions": "5.1",
|
|
||||||
"platforms": {
|
|
||||||
"ios": "9.0"
|
|
||||||
},
|
|
||||||
"source_files": "GradientLoadingBar/Classes/**/*",
|
|
||||||
"dependencies": {
|
|
||||||
"LightweightObservable": [
|
|
||||||
"~> 2.1"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"swift_version": "5.1"
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
PODS:
|
|
||||||
- GradientLoadingBar (2.2.3):
|
|
||||||
- LightweightObservable (~> 2.1)
|
|
||||||
- LightweightObservable (2.1.2)
|
|
||||||
- SnapshotTesting (1.9.0)
|
|
||||||
- SwiftConfigurationFiles (0.0.1)
|
|
||||||
- SwiftFormat/CLI (0.49.1)
|
|
||||||
- SwiftLint (0.45.1)
|
|
||||||
|
|
||||||
DEPENDENCIES:
|
|
||||||
- GradientLoadingBar (from `../`)
|
|
||||||
- SnapshotTesting (~> 1.7)
|
|
||||||
- SwiftConfigurationFiles (from `https://github.com/fxm90/SwiftConfigurationFiles.git`)
|
|
||||||
- SwiftFormat/CLI (~> 0.41)
|
|
||||||
- SwiftLint (~> 0.42)
|
|
||||||
|
|
||||||
SPEC REPOS:
|
|
||||||
https://github.com/CocoaPods/Specs.git:
|
|
||||||
- LightweightObservable
|
|
||||||
- SnapshotTesting
|
|
||||||
- SwiftFormat
|
|
||||||
- SwiftLint
|
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
|
||||||
GradientLoadingBar:
|
|
||||||
:path: "../"
|
|
||||||
SwiftConfigurationFiles:
|
|
||||||
:git: https://github.com/fxm90/SwiftConfigurationFiles.git
|
|
||||||
|
|
||||||
CHECKOUT OPTIONS:
|
|
||||||
SwiftConfigurationFiles:
|
|
||||||
:commit: 5e499075b42f5c986a6ad5a274cf8efa743337b5
|
|
||||||
:git: https://github.com/fxm90/SwiftConfigurationFiles.git
|
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
|
||||||
GradientLoadingBar: 157bd321c74e6bd0b0d87de735336060011393ab
|
|
||||||
LightweightObservable: c5ac85423a5edbed9a920b4d5c7b8f94ab3688c4
|
|
||||||
SnapshotTesting: 6141c48b6aa76ead61431ca665c14ab9a066c53b
|
|
||||||
SwiftConfigurationFiles: 1cf2228a911ebed9f42f8dec077bb634f04ca6c8
|
|
||||||
SwiftFormat: 16b41f3229f5e7edb130ac4cd631cceed7af7d5e
|
|
||||||
SwiftLint: 06ac37e4d38c7068e0935bb30cda95f093bec761
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: 36b90780cb508f9eaca4f0ac59660e3e748de601
|
|
||||||
|
|
||||||
COCOAPODS: 1.11.2
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Workspace
|
|
||||||
version = "1.0">
|
|
||||||
<FileRef
|
|
||||||
location = "self:">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IDEDidComputeMac32BitWarning</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Scheme
|
|
||||||
LastUpgradeVersion = "1020"
|
|
||||||
version = "1.3">
|
|
||||||
<BuildAction
|
|
||||||
parallelizeBuildables = "YES"
|
|
||||||
buildImplicitDependencies = "YES">
|
|
||||||
<BuildActionEntries>
|
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "YES"
|
|
||||||
buildForArchiving = "YES"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "A29550F398D14038E3DD88D91573885C"
|
|
||||||
BuildableName = "GradientLoadingBar.framework"
|
|
||||||
BlueprintName = "GradientLoadingBar"
|
|
||||||
ReferencedContainer = "container:Pods.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
|
||||||
</BuildAction>
|
|
||||||
<TestAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
||||||
<Testables>
|
|
||||||
</Testables>
|
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</TestAction>
|
|
||||||
<LaunchAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
launchStyle = "0"
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
|
||||||
debugDocumentVersioning = "YES"
|
|
||||||
debugServiceExtension = "internal"
|
|
||||||
allowLocationSimulation = "YES">
|
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "A29550F398D14038E3DD88D91573885C"
|
|
||||||
BuildableName = "GradientLoadingBar.framework"
|
|
||||||
BlueprintName = "GradientLoadingBar"
|
|
||||||
ReferencedContainer = "container:Pods.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</LaunchAction>
|
|
||||||
<ProfileAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
savedToolIdentifier = ""
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
debugDocumentVersioning = "YES">
|
|
||||||
</ProfileAction>
|
|
||||||
<AnalyzeAction
|
|
||||||
buildConfiguration = "Debug">
|
|
||||||
</AnalyzeAction>
|
|
||||||
<ArchiveAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
revealArchiveInOrganizer = "YES">
|
|
||||||
</ArchiveAction>
|
|
||||||
</Scheme>
|
|
||||||
@@ -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.
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
# 📸 SnapshotTesting
|
|
||||||
|
|
||||||
[](https://swift.org/download/)
|
|
||||||
[](https://actions-badge.atrox.dev/pointfreeco/swift-snapshot-testing/goto)
|
|
||||||
[](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)
|
|
||||||
// <?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
// <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
// <plist version="1.0">
|
|
||||||
// <dict>
|
|
||||||
// <key>bio</key>
|
|
||||||
// <string>Blobbed around the world.</string>
|
|
||||||
// <key>id</key>
|
|
||||||
// <integer>1</integer>
|
|
||||||
// <key>name</key>
|
|
||||||
// <string>Blobby</string>
|
|
||||||
// </dict>
|
|
||||||
// </plist>
|
|
||||||
```
|
|
||||||
|
|
||||||
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 🆓
|
|
||||||
|
|
||||||
<a href="https://www.pointfree.co/episodes/ep41-a-tour-of-snapshot-testing">
|
|
||||||
<img alt="video poster image" src="https://d1hf1soyumxcgv.cloudfront.net/0041-tour-of-snapshot-testing/0041-poster.jpg" width="480">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This library is released under the MIT license. See [LICENSE](LICENSE) for details.
|
|
||||||
@@ -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<Value>(
|
|
||||||
matching value: @autoclosure () throws -> Value,
|
|
||||||
as snapshotting: Snapshotting<Value, String>,
|
|
||||||
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<Value>(
|
|
||||||
matching value: @autoclosure () throws -> Value,
|
|
||||||
as snapshotting: Snapshotting<Value, String>,
|
|
||||||
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..<multiLineLiteralEndIndex, with: newDiffableLines)
|
|
||||||
|
|
||||||
recordings[context.fileName, default: []].append(fileRecording)
|
|
||||||
return context.setSourceCode(sourceCodeLines.joined(separator: "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.setSourceCode(sourceCodeLines.joined(separator: "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
internal struct FileRecording: Equatable {
|
|
||||||
let line: Int
|
|
||||||
let difference: Int
|
|
||||||
}
|
|
||||||
|
|
||||||
private func indentation<S: StringProtocol>(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 = [:]
|
|
||||||
@@ -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<Value, Format>(
|
|
||||||
matching value: @autoclosure () throws -> Value,
|
|
||||||
as snapshotting: Snapshotting<Value, Format>,
|
|
||||||
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<Value, Format>(
|
|
||||||
matching value: @autoclosure () throws -> Value,
|
|
||||||
as strategies: [String: Snapshotting<Value, Format>],
|
|
||||||
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<Value, Format>(
|
|
||||||
matching value: @autoclosure () throws -> Value,
|
|
||||||
as strategies: [Snapshotting<Value, Format>],
|
|
||||||
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<Value, Format>(
|
|
||||||
/// matching value: @autoclosure () throws -> Value,
|
|
||||||
/// as snapshotting: Snapshotting<Value, Format>,
|
|
||||||
/// 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<Value, Format>(
|
|
||||||
matching value: @autoclosure () throws -> Value,
|
|
||||||
as snapshotting: Snapshotting<Value, Format>,
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@@ -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<UIImage> { callback in
|
|
||||||
/// webView.takeSnapshot(with: nil) { image, error in
|
|
||||||
/// callback(image!)
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
public struct Async<Value> {
|
|
||||||
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<Value> into an Async<NewValue> with a function `(Value) -> NewValue`.
|
|
||||||
///
|
|
||||||
/// - Parameter f: A transformation to apply to the value wrapped by the async value.
|
|
||||||
public func map<NewValue>(_ f: @escaping (Value) -> NewValue) -> Async<NewValue> {
|
|
||||||
return .init { callback in
|
|
||||||
self.run { a in callback(f(a)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<View>] {
|
|
||||||
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<Image>? {
|
|
||||||
func inWindow<T>(_ 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<Image> { 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<UIImage> {
|
|
||||||
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<A>() -> Async<[A]> where Element == Async<A> {
|
|
||||||
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])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#if os(Linux)
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public struct XCTAttachment {
|
|
||||||
public init(data: Data) {}
|
|
||||||
public init(data: Data, uniformTypeIdentifier: String) {}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
struct Difference<A> {
|
|
||||||
enum Which {
|
|
||||||
case first
|
|
||||||
case second
|
|
||||||
case both
|
|
||||||
}
|
|
||||||
|
|
||||||
let elements: [A]
|
|
||||||
let which: Which
|
|
||||||
}
|
|
||||||
|
|
||||||
func diff<A: Hashable>(_ fst: [A], _ snd: [A]) -> [Difference<A>] {
|
|
||||||
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<String>], 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<String>).map(prepending(figureSpace))
|
|
||||||
)
|
|
||||||
return (next, changed(hunk) ? hunks + [hunk] : hunks)
|
|
||||||
case .both where current.lines.isEmpty:
|
|
||||||
let lines = (diff.elements.suffix(ctx) as ArraySlice<String>).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
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import XCTest
|
|
||||||
|
|
||||||
/// The ability to compare `Value`s and convert them to and from `Data`.
|
|
||||||
public struct Diffing<Value> {
|
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import XCTest
|
|
||||||
|
|
||||||
@available(swift, obsoleted: 5.0, renamed: "XCTestCase", message: "Please use XCTestCase instead")
|
|
||||||
public typealias SnapshotTestCase = XCTestCase
|
|
||||||
@@ -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<Value, Format> {
|
|
||||||
/// 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<Format>
|
|
||||||
|
|
||||||
/// How a value is transformed into a diffable snapshot format.
|
|
||||||
public var snapshot: (Value) -> Async<Format>
|
|
||||||
|
|
||||||
/// 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<Format>,
|
|
||||||
asyncSnapshot: @escaping (_ value: Value) -> Async<Format>
|
|
||||||
) {
|
|
||||||
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<Format>,
|
|
||||||
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<Value, Format>` into a `Snapshotting<NewValue, Format>` 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<Value, Format>) -> Snapshotting<NewValue, Format>`.
|
|
||||||
///
|
|
||||||
/// A simple example of this is to `pullback` the snapshot strategy on `UIView`s to work on `UIViewController`s:
|
|
||||||
///
|
|
||||||
/// let strategy = Snapshotting<UIView, UIImage>.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<NewValue>(_ transform: @escaping (_ otherValue: NewValue) -> Value) -> Snapshotting<NewValue, Format> {
|
|
||||||
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<Value>`.
|
|
||||||
///
|
|
||||||
/// 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<Value>`, which is necessary when your transformation needs to perform some asynchronous work.
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - transform: A transform function from `NewValue` into `Async<Value>`.
|
|
||||||
/// - otherValue: A value to be transformed.
|
|
||||||
public func asyncPullback<NewValue>(_ transform: @escaping (_ otherValue: NewValue) -> Async<Value>)
|
|
||||||
-> Snapshotting<NewValue, Format> {
|
|
||||||
|
|
||||||
return Snapshotting<NewValue, Format>(
|
|
||||||
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<Format> = Snapshotting<Format, Format>
|
|
||||||
|
|
||||||
extension Snapshotting where Value == Format {
|
|
||||||
public init(pathExtension: String?, diffing: Diffing<Format>) {
|
|
||||||
self.init(
|
|
||||||
pathExtension: pathExtension,
|
|
||||||
diffing: diffing,
|
|
||||||
snapshot: { $0 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<T>(_ 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)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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`<A>(into witness: Snapshotting<A, Format>) -> Snapshotting<(Value) -> A, Format> {
|
|
||||||
var snapshotting = Snapshotting<String, String>.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||