├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ ├── question.md
│ └── bug_report.md
└── PULL_REQUEST_TEMPLATE.md
├── Example Apps
├── iOS Example
│ ├── App
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ └── Base.lproj
│ │ │ └── LaunchScreen.storyboard
│ ├── Models
│ │ └── IndicatorPresetModel.swift
│ ├── Info.plist
│ └── Scenes
│ │ └── PresetsController.swift
├── tvOS Example
│ ├── App
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ └── App Icon & Top Shelf Image.brandassets
│ │ │ │ ├── App Icon.imagestack
│ │ │ │ ├── Back.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── Front.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── Middle.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ │ ├── App Icon - App Store.imagestack
│ │ │ │ ├── Back.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── Front.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ ├── Middle.imagestacklayer
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Content.imageset
│ │ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ │ ├── Top Shelf Image.imageset
│ │ │ │ └── Contents.json
│ │ │ │ ├── Top Shelf Image Wide.imageset
│ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── SPIndicator.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── swiftpm
│ │ │ └── Package.resolved
│ ├── xcuserdata
│ │ └── ivanvorobei.xcuserdatad
│ │ │ ├── xcdebugger
│ │ │ └── Breakpoints_v2.xcbkptlist
│ │ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── iOS Example.xcscheme
│ └── project.pbxproj
└── AppImport
│ ├── Package.swift
│ └── Sources
│ └── AppImport
│ └── Example.swift
├── .gitignore
├── TODO.md
├── Package.swift
├── SPIndicator.podspec
├── CONTRIBUTING.md
├── LICENSE
├── Sources
└── SPIndicator
│ ├── Models
│ ├── SPIndicatorPresentSide.swift
│ └── SPIndicatorLayout.swift
│ ├── Protocols
│ └── SPIndicatorIconAnimatable.swift
│ ├── Extensions
│ ├── UIFontExtension.swift
│ ├── UILabelExtension.swift
│ ├── UIColorExtension.swift
│ └── SwiftUIExtension.swift
│ ├── Services
│ └── SPIndicatorHaptic.swift
│ ├── Icons
│ ├── SPIndicatorIconDoneView.swift
│ └── SPAlertIconErrorView.swift
│ ├── SPIndicator.swift
│ ├── SPIndicatorIconPreset.swift
│ └── SPIndicatorView.swift
├── CODE_OF_CONDUCT.md
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [ivanvorobei, sparrowcode]
4 |
--------------------------------------------------------------------------------
/Example Apps/iOS Example/App/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SparrowKit
3 |
4 | @main
5 | class AppDelegate: SPAppWindowDelegate {}
6 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # osX files
2 | .DS_Store
3 | .Trashes
4 |
5 | # Swift Package Manager
6 | .swiftpm
7 |
8 | # User Interface
9 | *UserInterfaceState.xcuserstate
10 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example Apps/SPIndicator.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example Apps/SPIndicator.xcodeproj/xcuserdata/ivanvorobei.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | Here provided ideas or features which will be implemented soon.
4 |
5 | - Add grades for left and right side when text not fit, including scroll animation for label
6 | - Add tvOS version (left-top corner) & docs.
7 | - If appear more show like stack.
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ivanvorobei
7 |
8 | ---
9 |
10 | **Feature Description**
11 | Describe what functionality you want to see.
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Something is not clear with the project
4 | title: ''
5 | labels: question
6 | assignees: ivanvorobei
7 |
8 | ---
9 |
10 | **Describe the Problem**
11 | A clear and concise description of what you want to do.
12 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Goal
2 |
3 |
4 | ## Checklist
5 |
6 | - [] Testing in compability platforms
7 | - [] Installed correct via `Swift Package Manager` and `Cocoapods`
8 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Example Apps/SPIndicator.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example Apps/SPIndicator.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ivanvorobei
7 | ---
8 |
9 | **Details**
10 | - iOS Version [e.g. 15.2]
11 | - Framework Version [e.g. 1.0.2]
12 | - Installed via [e.g. SPM, Cocoapods, Manually]
13 |
14 | **Describe the Bug**
15 | A clear and concise description of what the bug is.
16 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "SPIndicator",
7 | platforms: [
8 | .iOS(.v12), .tvOS(.v12)
9 | ],
10 | products: [
11 | .library(
12 | name: "SPIndicator",
13 | targets: ["SPIndicator"]
14 | )
15 | ],
16 | dependencies: [],
17 | targets: [
18 | .target(
19 | name: "SPIndicator",
20 | swiftSettings: [
21 | .define("SPINDICATOR_SPM")
22 | ]
23 | )
24 | ],
25 | swiftLanguageVersions: [.v5]
26 | )
27 |
--------------------------------------------------------------------------------
/SPIndicator.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 |
3 | s.name = 'SPIndicator'
4 | s.version = '1.6.4'
5 | s.summary = 'Floating indicator, mimicrate to indicator which appear when silent mode switched. Can be present from top and bottom.'
6 | s.homepage = 'https://github.com/ivanvorobei/SPIndicator'
7 | s.source = { :git => 'https://github.com/ivanvorobei/SPIndicator.git', :tag => s.version }
8 | s.license = { :type => "MIT", :file => "LICENSE" }
9 | s.author = { "Ivan Vorobei" => "hello@ivanvorobei.io" }
10 |
11 | s.swift_version = '5.1'
12 | s.ios.deployment_target = '12.0'
13 | s.tvos.deployment_target = '12.0'
14 |
15 | s.source_files = 'Sources/SPIndicator/**/*.swift'
16 |
17 | end
18 |
--------------------------------------------------------------------------------
/Example Apps/SPIndicator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "SparrowKit",
6 | "repositoryURL": "https://github.com/ivanvorobei/SparrowKit",
7 | "state": {
8 | "branch": null,
9 | "revision": "f872b45a3e6aa5e2833ac8d3050a3f026d97ce0b",
10 | "version": "3.5.2"
11 | }
12 | },
13 | {
14 | "package": "SPDiffable",
15 | "repositoryURL": "https://github.com/ivanvorobei/SPDiffable",
16 | "state": {
17 | "branch": null,
18 | "revision": "096ffb560906f762db4798ffa932d1c116ec8e9d",
19 | "version": "4.0.1"
20 | }
21 | }
22 | ]
23 | },
24 | "version": 1
25 | }
26 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Here provided more info about project, contribution process and recomended changes.
4 | Please, read it before pull request or create issue.
5 |
6 | ## Codestyle
7 |
8 | ### Marks
9 |
10 | For clean struct code good is using marks.
11 |
12 | ```swift
13 | class Example {
14 |
15 | // MARK: - Init
16 |
17 | init() {}
18 | }
19 | ```
20 |
21 | Here you find all which using in project:
22 |
23 | - // MARK: - Init
24 | - // MARK: - Lifecycle
25 | - // MARK: - Layout
26 | - // MARK: - Public
27 | - // MARK: - Private
28 | - // MARK: - Internal
29 | - // MARK: - Models
30 | - // MARK: - Ovveride
31 |
32 | If you can't find valid, add new to codestyle agreements please. Other can be use if class is large and need struct it even without adding to codestyle agreements.
33 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "filename" : "App Icon - App Store.imagestack",
5 | "idiom" : "tv",
6 | "role" : "primary-app-icon",
7 | "size" : "1280x768"
8 | },
9 | {
10 | "filename" : "App Icon.imagestack",
11 | "idiom" : "tv",
12 | "role" : "primary-app-icon",
13 | "size" : "400x240"
14 | },
15 | {
16 | "filename" : "Top Shelf Image Wide.imageset",
17 | "idiom" : "tv",
18 | "role" : "top-shelf-image-wide",
19 | "size" : "2320x720"
20 | },
21 | {
22 | "filename" : "Top Shelf Image.imageset",
23 | "idiom" : "tv",
24 | "role" : "top-shelf-image",
25 | "size" : "1920x720"
26 | }
27 | ],
28 | "info" : {
29 | "author" : "xcode",
30 | "version" : 1
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Example Apps/AppImport/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.4
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "AppImport",
7 | platforms: [
8 | .iOS(.v12),
9 | .tvOS(.v12),
10 | .watchOS(.v3)
11 | ],
12 | products: [
13 | .library(name: "AppImport", targets: ["AppImport"]),
14 | ],
15 | dependencies: [
16 | .package(name: "SparrowKit", url: "https://github.com/ivanvorobei/SparrowKit", .upToNextMajor(from: "3.5.1")),
17 | .package(name: "SPDiffable", url: "https://github.com/ivanvorobei/SPDiffable", .upToNextMajor(from: "4.0.1")),
18 | .package(name: "SPIndicator", path: "SPIndicator")
19 | ],
20 | targets: [
21 | .target(
22 | name: "AppImport",
23 | dependencies: [
24 | .product(name: "SparrowKit", package: "SparrowKit"),
25 | .product(name: "SPDiffable", package: "SPDiffable"),
26 | .product(name: "SPIndicator", package: "SPIndicator")
27 | ]
28 | ),
29 | ]
30 | )
31 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIRequiredDeviceCapabilities
26 |
27 | arm64
28 |
29 | UIUserInterfaceStyle
30 | Automatic
31 |
32 |
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Ivan Vorobei
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Example Apps/SPIndicator.xcodeproj/xcuserdata/ivanvorobei.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | iOS Example.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | tvOS Example.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 3
16 |
17 | watchOS Example.xcscheme_^#shared#^_
18 |
19 | orderHint
20 | 3
21 |
22 |
23 | SuppressBuildableAutocreation
24 |
25 | F43F827A26578D83001D9B3D
26 |
27 | primary
28 |
29 |
30 | F43F82C026578F22001D9B3D
31 |
32 | primary
33 |
34 |
35 | F43F82EE26578FAB001D9B3D
36 |
37 | primary
38 |
39 |
40 | F43F8319265791A0001D9B3D
41 |
42 | primary
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Example Apps/AppImport/Sources/AppImport/Example.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import Foundation
23 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/Models/SPIndicatorPresentSide.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import Foundation
23 |
24 | /**
25 | SPIndicator: Cases from preset styles.
26 | */
27 | public enum SPIndicatorPresentSide {
28 |
29 | case top
30 | case center
31 | case bottom
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/Protocols/SPIndicatorIconAnimatable.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 |
24 | /**
25 | SPIndicator: Protocol for animatable views.
26 | */
27 | public protocol SPIndicatorIconAnimatable {
28 |
29 | /**
30 | SPIndicator: Shoud call when need start animation.
31 | */
32 | func animate()
33 | }
34 |
--------------------------------------------------------------------------------
/Example Apps/iOS Example/Models/IndicatorPresetModel.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 | import SPIndicator
24 |
25 | /**
26 | Example wrapper model.
27 | */
28 | struct IndicatorPresetModel {
29 |
30 | var name: String
31 | var title: String
32 | var message: String?
33 | var preset: SPIndicatorIconPreset?
34 |
35 | var id: String {
36 | return name
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Example Apps/iOS Example/App/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 | import SparrowKit
24 |
25 | @UIApplicationMain
26 | class AppDelegate: SPAppWindowDelegate {
27 |
28 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
29 | let rootController = PresetsController().wrapToNavigationController(prefersLargeTitles: false)
30 | makeKeyAndVisible(viewController: rootController, tint: .systemBlue)
31 | return true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/Extensions/UIFontExtension.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 |
24 | extension UIFont {
25 |
26 | static func preferredFont(forTextStyle style: TextStyle, weight: Weight, addPoints: CGFloat = 0) -> UIFont {
27 | let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style)
28 | let font = UIFont.systemFont(ofSize: descriptor.pointSize + addPoints, weight: weight)
29 | let metrics = UIFontMetrics(forTextStyle: style)
30 | return metrics.scaledFont(for: font)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Example Apps/iOS Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | SPIndicator
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UIApplicationSupportsIndirectInputEvents
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/Models/SPIndicatorLayout.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 |
24 | /**
25 | SPIndicator: Layout model for view.
26 | */
27 | open class SPIndicatorLayout {
28 |
29 | /**
30 | SPIndicator: Icon size.
31 | */
32 | open var iconSize: CGSize
33 |
34 | /**
35 | SPIndicator: Alert margings for each side.
36 | */
37 | open var margins: UIEdgeInsets
38 |
39 | public init(iconSize: CGSize, margins: UIEdgeInsets) {
40 | self.iconSize = iconSize
41 | self.margins = margins
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Example Apps/iOS Example/App/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/Extensions/UILabelExtension.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 |
24 | extension UILabel {
25 |
26 | func layoutDynamicHeight(width: CGFloat) {
27 | layoutDynamicHeight(x: frame.origin.x, y: frame.origin.y, width: width)
28 | }
29 |
30 | func layoutDynamicHeight(x: CGFloat, y: CGFloat, width: CGFloat) {
31 | frame = CGRect.init(x: x, y: y, width: width, height: frame.height)
32 | sizeToFit()
33 | if frame.width != width {
34 | frame = .init(x: x, y: y, width: width, height: frame.height)
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Example Apps/tvOS Example/App/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Example Apps/iOS Example/App/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/Services/SPIndicatorHaptic.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 |
24 | /**
25 | SPIndicator: Wrapper of haptic styles.
26 | */
27 | public enum SPIndicatorHaptic {
28 |
29 | case success
30 | case warning
31 | case error
32 | case none
33 |
34 | func impact() {
35 | #if os(iOS)
36 | let generator = UINotificationFeedbackGenerator()
37 | switch self {
38 | case .success:
39 | generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.success)
40 | case .warning:
41 | generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.warning)
42 | case .error:
43 | generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.error)
44 | case .none:
45 | break
46 | }
47 | #endif
48 | }
49 | }
50 |
51 |
52 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/Icons/SPIndicatorIconDoneView.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 |
24 | public class SPIndicatorIconDoneView: UIView, SPIndicatorIconAnimatable {
25 |
26 | public func animate() {
27 | let length = frame.width
28 | let animatablePath = UIBezierPath()
29 | animatablePath.move(to: CGPoint(x: length * 0.196, y: length * 0.527))
30 | animatablePath.addLine(to: CGPoint(x: length * 0.47, y: length * 0.777))
31 | animatablePath.addLine(to: CGPoint(x: length * 0.99, y: length * 0.25))
32 |
33 | let animatableLayer = CAShapeLayer()
34 | animatableLayer.path = animatablePath.cgPath
35 | animatableLayer.fillColor = UIColor.clear.cgColor
36 | animatableLayer.strokeColor = tintColor?.cgColor
37 | animatableLayer.lineWidth = 4
38 | animatableLayer.lineCap = .round
39 | animatableLayer.lineJoin = .round
40 | animatableLayer.strokeEnd = 0
41 | layer.addSublayer(animatableLayer)
42 |
43 | let animation = CABasicAnimation(keyPath: "strokeEnd")
44 | animation.duration = 0.25
45 | animation.fromValue = 0
46 | animation.toValue = 1
47 | animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
48 | animatableLayer.strokeEnd = 1
49 | animatableLayer.add(animation, forKey: "animation")
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Example Apps/SPIndicator.xcodeproj/xcshareddata/xcschemes/iOS Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/Extensions/UIColorExtension.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 |
24 | extension UIColor {
25 |
26 | enum Compability {
27 |
28 | static var systemBackground: UIColor {
29 | #if os(iOS)
30 | if #available(iOS 13.0, *) {
31 | return UIColor.systemBackground
32 | }
33 | #endif
34 | return UIColor.white
35 | }
36 |
37 | static var secondarySystemBackground: UIColor {
38 | #if os(iOS)
39 | if #available(iOS 13.0, *) {
40 | return UIColor.secondarySystemBackground
41 | }
42 | #endif
43 | return UIColor(red: 242/255, green: 242/255, blue: 247/255, alpha: 1)
44 | }
45 |
46 | static var separator: UIColor {
47 | if #available(iOS 13.0, tvOS 13.0, *) {
48 | return UIColor.separator
49 | } else {
50 | return UIColor(red: 60/255, green: 60/255, blue: 67/255, alpha: 1)
51 | }
52 | }
53 |
54 | static var label: UIColor {
55 | if #available(iOS 13.0, tvOS 13.0, *) {
56 | return UIColor.label
57 | } else {
58 | return UIColor.black
59 | }
60 | }
61 |
62 | static var secondaryLabel: UIColor {
63 | if #available(iOS 13.0, tvOS 13.0, *) {
64 | return UIColor.secondaryLabel
65 | } else {
66 | return UIColor(red: 138/255, green: 138/255, blue: 142/255, alpha: 1)
67 | }
68 | }
69 | }
70 |
71 | static var buttonArea: UIColor {
72 | if #available(iOS 13.0, tvOS 13.0, *) {
73 | return UIColor { (traits) -> UIColor in
74 | return traits.userInterfaceStyle == .dark ? UIColor(red: 61/255, green: 62/255, blue: 66/255, alpha: 1) :
75 | UIColor(red: 238/255, green: 238/255, blue: 240/255, alpha: 1)
76 | }
77 | } else {
78 | return UIColor(red: 238/255, green: 238/255, blue: 240/255, alpha: 1)
79 | }
80 | }
81 |
82 | @available(iOSApplicationExtension, unavailable)
83 | static var tint: UIColor {
84 | get {
85 | let value = UIApplication.shared.windows.first?.tintColor
86 | guard let tint = value else { return .systemBlue }
87 | return tint
88 | }
89 | set {
90 | UIApplication.shared.windows.forEach({ $0.tintColor = newValue })
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/Icons/SPAlertIconErrorView.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 |
24 | public class SPIndicatorIconErrorView: UIView, SPIndicatorIconAnimatable {
25 |
26 | public func animate() {
27 | animateTopToBottomLine()
28 | animateBottomToTopLine()
29 | }
30 |
31 | private func animateTopToBottomLine() {
32 | let length = frame.width
33 |
34 | let topToBottomLine = UIBezierPath()
35 | topToBottomLine.move(to: CGPoint(x: length * 0, y: length * 0))
36 | topToBottomLine.addLine(to: CGPoint(x: length * 1, y: length * 1))
37 |
38 | let animatableLayer = CAShapeLayer()
39 | animatableLayer.path = topToBottomLine.cgPath
40 | animatableLayer.fillColor = UIColor.clear.cgColor
41 | animatableLayer.strokeColor = tintColor?.cgColor
42 | animatableLayer.lineWidth = 4
43 | animatableLayer.lineCap = .round
44 | animatableLayer.lineJoin = .round
45 | animatableLayer.strokeEnd = 0
46 | self.layer.addSublayer(animatableLayer)
47 |
48 | let animation = CABasicAnimation(keyPath: "strokeEnd")
49 | animation.duration = 0.22
50 | animation.fromValue = 0
51 | animation.toValue = 1
52 | animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
53 |
54 | animatableLayer.strokeEnd = 1
55 | animatableLayer.add(animation, forKey: "animation")
56 | }
57 |
58 | private func animateBottomToTopLine() {
59 | let length = frame.width
60 |
61 | let bottomToTopLine = UIBezierPath()
62 | bottomToTopLine.move(to: CGPoint(x: length * 0, y: length * 1))
63 | bottomToTopLine.addLine(to: CGPoint(x: length * 1, y: length * 0))
64 |
65 | let animatableLayer = CAShapeLayer()
66 | animatableLayer.path = bottomToTopLine.cgPath
67 | animatableLayer.fillColor = UIColor.clear.cgColor
68 | animatableLayer.strokeColor = tintColor?.cgColor
69 | animatableLayer.lineWidth = 4
70 | animatableLayer.lineCap = .round
71 | animatableLayer.lineJoin = .round
72 | animatableLayer.strokeEnd = 0
73 | self.layer.addSublayer(animatableLayer)
74 |
75 | let animation = CABasicAnimation(keyPath: "strokeEnd")
76 | animation.duration = 0.22
77 | animation.fromValue = 0
78 | animation.toValue = 1
79 | animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
80 |
81 | animatableLayer.strokeEnd = 1
82 | animatableLayer.add(animation, forKey: "animation")
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/Extensions/SwiftUIExtension.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import SwiftUI
23 |
24 | #if os(iOS)
25 |
26 | @available(iOS 13.0, *)
27 | @available(iOSApplicationExtension, unavailable)
28 |
29 | extension View {
30 |
31 | public func SPIndicator(
32 | isPresent: Binding,
33 | indicatorView: SPIndicatorView,
34 | duration: TimeInterval = 2.0,
35 | haptic: SPIndicatorHaptic = .none
36 | ) -> some View {
37 |
38 | if isPresent.wrappedValue {
39 | let indicatorCompletion = indicatorView.completion
40 | let indicatorDismiss = {
41 | isPresent.wrappedValue = false
42 | indicatorCompletion?()
43 | }
44 | indicatorView.duration = duration
45 | indicatorView.present(haptic: haptic, completion: indicatorDismiss)
46 | }
47 | return self
48 | }
49 |
50 | public func SPIndicator(
51 | isPresent: Binding,
52 | title: String = "",
53 | message: String? = nil,
54 | duration: TimeInterval = 2.0,
55 | presentSide: SPIndicatorPresentSide = .top,
56 | dismissByDrag: Bool = true,
57 | preset: SPIndicatorIconPreset = .done,
58 | haptic: SPIndicatorHaptic = .none,
59 | layout: SPIndicatorLayout? = nil,
60 | completion: (()-> Void)? = nil
61 | ) -> some View {
62 |
63 | let indicatorView = SPIndicatorView(title: title, message: message, preset: preset)
64 | indicatorView.presentSide = presentSide
65 | indicatorView.dismissByDrag = dismissByDrag
66 | indicatorView.layout = layout ?? SPIndicatorLayout(for: preset)
67 | indicatorView.completion = completion
68 | return SPIndicator(isPresent: isPresent, indicatorView: indicatorView, duration: duration, haptic: haptic)
69 | }
70 |
71 | public func SPIndicator(
72 | isPresent: Binding,
73 | title: String,
74 | duration: TimeInterval = 2.0,
75 | presentSide: SPIndicatorPresentSide = .top,
76 | dismissByDrag: Bool = true,
77 | preset: SPIndicatorIconPreset = .done,
78 | haptic: SPIndicatorHaptic = .none,
79 | completion: (()-> Void)? = nil
80 | ) -> some View {
81 |
82 | let indicatorView = SPIndicatorView(title: title, preset: preset)
83 | indicatorView.presentSide = presentSide
84 | indicatorView.dismissByDrag = dismissByDrag
85 | indicatorView.completion = completion
86 | return SPIndicator(isPresent: isPresent, indicatorView: indicatorView, duration: duration, haptic: haptic)
87 | }
88 |
89 | }
90 | #endif
91 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/SPIndicator.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 |
24 | @available(iOSApplicationExtension, unavailable)
25 | public enum SPIndicator {
26 |
27 | #if os(iOS)
28 |
29 | /**
30 | SPIndicator: Present alert with preset and custom haptic.
31 |
32 | - parameter title: Title text in alert.
33 | - parameter message: Subtitle text in alert. Optional.
34 | - parameter preset: Icon ready-use style or custom image.
35 | - parameter haptic: Haptic response with present. Default is `.success`.
36 | - parameter presentSide: Choose from side appear indicator.
37 | - parameter completion: Will call with dismiss alert.
38 | */
39 | public static func present(title: String, message: String? = nil, preset: SPIndicatorIconPreset, haptic: SPIndicatorHaptic, from presentSide: SPIndicatorPresentSide = .top, completion: (() -> Void)? = nil) {
40 | let alertView = SPIndicatorView(title: title, message: message, preset: preset)
41 | alertView.presentSide = presentSide
42 | alertView.present(haptic: haptic, completion: completion)
43 | }
44 |
45 | /**
46 | SPIndicator: Present alert with preset and automatically detect type haptic.
47 |
48 | - parameter title: Title text in alert.
49 | - parameter message: Subtitle text in alert. Optional.
50 | - parameter preset: Icon ready-use style or custom image.
51 | - parameter presentSide: Choose from side appear indicator. Default is `.top`.
52 | - parameter completion: Will call with dismiss alert.
53 | */
54 | public static func present(title: String, message: String? = nil, preset: SPIndicatorIconPreset, from presentSide: SPIndicatorPresentSide = .top, completion: (() -> Void)? = nil) {
55 | let alertView = SPIndicatorView(title: title, message: message, preset: preset)
56 | alertView.presentSide = presentSide
57 | let haptic = preset.getHaptic()
58 | alertView.present(haptic: haptic, completion: completion)
59 | }
60 |
61 | /**
62 | SPIndicator: Show only message, without title and icon.
63 |
64 | - parameter message: Title text.
65 | - parameter haptic: Haptic response with present. Default is `.success`.
66 | - parameter presentSide: Choose from side appear indicator. Default is `.top`.
67 | - parameter completion: Will call with dismiss alert.
68 | */
69 | public static func present(title: String, message: String? = nil, haptic: SPIndicatorHaptic, from presentSide: SPIndicatorPresentSide = .top, completion: (() -> Void)? = nil) {
70 | let alertView = SPIndicatorView(title: title, message: message)
71 | alertView.presentSide = presentSide
72 | alertView.present(haptic: haptic, completion: completion)
73 | }
74 |
75 | #endif
76 | }
77 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/SPIndicatorIconPreset.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 |
24 | /**
25 | SPIndicator: Represent icon wrapper.
26 | Included default styles and can be custom image.
27 | */
28 | public enum SPIndicatorIconPreset {
29 |
30 | case done
31 | case error
32 | case spin(_ style: UIActivityIndicatorView.Style)
33 | case custom(_ image: UIImage)
34 | }
35 |
36 | // Get view and haptic by Preset.
37 |
38 | public extension SPIndicatorIconPreset {
39 |
40 | func createView() -> UIView {
41 | switch self {
42 | case .done:
43 | let view = SPIndicatorIconDoneView()
44 | return view
45 | case .error:
46 | let view = SPIndicatorIconErrorView()
47 | view.tintColor = UIColor.systemRed
48 | return view
49 | case .spin(let style):
50 | let view = UIActivityIndicatorView(style: style)
51 | view.startAnimating()
52 | return view
53 | case .custom(let image):
54 | let imageView = UIImageView(image: image)
55 | imageView.contentMode = .scaleAspectFit
56 | return imageView
57 | }
58 | }
59 |
60 | func getHaptic() -> SPIndicatorHaptic {
61 | switch self {
62 | case .error: return .error
63 | case .done: return .success
64 | case .spin(_): return .none
65 | case .custom(_): return .success
66 | }
67 | }
68 | }
69 |
70 | // Get layout by preset.
71 |
72 | public extension SPIndicatorLayout {
73 |
74 | convenience init() {
75 | self.init(
76 | iconSize: .init(
77 | width: Self.defaultIconSideSize,
78 | height: Self.defaultIconSideSize
79 | ),
80 | margins: .init(
81 | top: Self.defaultVerticallInset,
82 | left: Self.defaultHorizontalInset,
83 | bottom: Self.defaultVerticallInset,
84 | right: Self.defaultHorizontalInset
85 | )
86 | )
87 | }
88 |
89 | static func message() -> SPIndicatorLayout {
90 | let layout = SPIndicatorLayout()
91 | return layout
92 | }
93 |
94 | convenience init(for preset: SPIndicatorIconPreset) {
95 | switch preset {
96 | case .done:
97 | self.init()
98 | iconSize = .init(width: 24, height: 24)
99 | case .error:
100 | self.init()
101 | iconSize = .init(width: 14, height: 14)
102 | margins.left = 19
103 | margins.right = margins.left
104 | case .spin(_):
105 | self.init()
106 | case .custom(_):
107 | self.init()
108 | }
109 | }
110 |
111 | // Default values.
112 |
113 | private static var defaultIconSideSize: CGFloat { 28 }
114 | private static var defaultSpaceBetweenIconAndTitle: CGFloat { 26 }
115 | private static var defaultVerticallInset: CGFloat { 8 }
116 | private static var defaultHorizontalInset: CGFloat { 15 }
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | hello@ivanvorobei.io.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SPIndicator
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Mimicrate for the indicator which appears when silent mode is turned on / off. Availabe 2 animated presets: `done` & `error`. Also supports custom images and presentations from top, center & bottom sides.
10 |
11 | To get an alert like in Apple music, use this library instead [SPAlert](https://github.com/ivanvorobei/SPAlert).
12 |
13 | ## Navigate
14 |
15 | - [Installation](#installation)
16 | - [Swift Package Manager](#swift-package-manager)
17 | - [CocoaPods](#cocoapods)
18 | - [Manually](#manually)
19 | - [Quick Start](#quick-start)
20 | - [Usage](#usage)
21 | - [Duration](#duration)
22 | - [Layout](#layout)
23 | - [Dismiss by Drag](#dismiss-by-drag)
24 | - [Haptic](#haptic)
25 | - [Present Side](#present-side)
26 | - [Shared Configuration](#shared-configuration)
27 | - [Swift UI](#swiftui)
28 | - [Russian Community](#russian-community)
29 |
30 | ## Installation
31 |
32 | Ready for use on iOS 12+ & tvOS 12+.
33 |
34 | ### Swift Package Manager
35 |
36 | 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’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
37 |
38 | Once you have your Swift package set up, adding as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
39 |
40 | ```swift
41 | dependencies: [
42 | .package(url: "https://github.com/ivanvorobei/SPIndicator", .upToNextMajor(from: "1.6.0"))
43 | ]
44 | ```
45 |
46 | ### CocoaPods:
47 |
48 | [CocoaPods](https://cocoapods.org) is a dependency manager. For usage and installation instructions, visit their website. To integrate using CocoaPods, specify it in your `Podfile`:
49 |
50 | ```ruby
51 | pod 'SPIndicator'
52 | ```
53 |
54 | ### Manually
55 |
56 | If you prefer not to use any of dependency managers, you can integrate manually. Put `Sources/SPIndicator` folder in your Xcode project. Make sure to enable `Copy items if needed` and `Create groups`.
57 |
58 | ## Quick Start
59 |
60 | For best experience, I recommend presenting indicator by calling the class functions `SPIndicator`. These functions are updated regularly and show the indicator as Apple way:
61 |
62 | ```swift
63 | // Presets:
64 | SPIndicator.present(title: "Error", message: "Try Again", preset: .error)
65 |
66 | // For show with custom image:
67 | let image = UIImage.init(systemName: "sun.min.fill")!.withTintColor(.systemYellow, renderingMode: .alwaysOriginal)
68 | SPIndicator.present(title: "Custom Image", message: "With tint color", preset: .custom(image))
69 |
70 | // For show text only:
71 | SPIndicator.present(title: "Error", haptic: .error)
72 | ```
73 |
74 | ## Usage
75 |
76 | ### Duration
77 |
78 | To change the presentation duration time, create the indicator view and call the method `present` with a custom duration:
79 |
80 | ```swift
81 | let indicatorView = SPIndicatorView(title: "Complete", preset: .done)
82 | indicatorView.present(duration: 3)
83 | ```
84 |
85 | ### Layout
86 |
87 | To customise the layout & margins use the `layout` property. You can manage margins for each side, icon size and space between image and titles:
88 |
89 | ```swift
90 | indicatorView.layout.iconSize = .init(width: 24, height: 24)
91 | indicatorView.layout.margins.top = 12
92 | ```
93 |
94 | ### Dismiss by Drag
95 |
96 | By default, you can drag the indicator to hide it. While the indicator is dragging, dismiss is not working. This behaviour can be disabled:
97 |
98 | ```swift
99 | indicatorView.dismissByDrag = false
100 | ```
101 |
102 | ### Haptic
103 |
104 | To manage haptics, you shoud pass it in present method:
105 |
106 | ```swift
107 | indicatorView.present(duration: 1.5, haptic: .success, completion: nil)
108 | ```
109 |
110 | You can remove the duration and completion parameters from the init. They have default values.
111 |
112 | ### Present Side
113 |
114 | You can change the presentation side:
115 |
116 | ```swift
117 | SPIndicator.present(title: "Error", message: "Try Again", preset: .error, from: .bottom)
118 |
119 | // or for custom `SPIndicatorView`
120 |
121 | indicatorView.presentSide = .bottom
122 | ```
123 | In the case from above, the indicator will appear from bottom and will be attached to bottom. To manage the offset - check the property `offset`.
124 |
125 | ### Shared Configuration
126 |
127 | Also, you can change some default values for the alerts. For example, you can change the default duration for an alert with this code:
128 |
129 | ```swift
130 | SPIndicatorView.appearance().duration = 2
131 | ```
132 |
133 | It will apply for all alerts. I recommend to set it in app delegate, but you can change it in runtime.
134 |
135 | ## SwiftUI
136 |
137 | Use like system alert only show message tips:
138 |
139 | ```swift
140 | Button("Show Indicator") {
141 | showIndicator = true
142 | }.SPIndicator(isPresent: $showIndicator, title: "This is title only")
143 | ```
144 |
145 | or show message, title, image and other configuration:
146 |
147 | ```swift
148 | Button("Show Indicator") {
149 | showIndicator = true
150 | }.SPIndicator(
151 | isPresent: $showIndicator,
152 | title: "Title",
153 | message: "Message",
154 | duration: 2.0,
155 | presentSide: .top,
156 | dismissByDrag: false,
157 | preset: .custom(UIImage(systemName: "heart")!),
158 | haptic: .success,
159 | layout: .init(),
160 | completion: {
161 | print("Indicator is destoryed")
162 | })
163 | ```
164 |
165 | ## Russian Community
166 |
167 | Я веду [телеграм-канал](https://sparrowcode.io/telegram), там публикую новости и туториалы.
168 | С проблемой помогут [в чате](https://sparrowcode.io/telegram/chat).
169 |
170 | Видео-туториалы выклыдываю на [YouTube](https://ivanvorobei.io/youtube):
171 |
172 | [](https://ivanvorobei.io/youtube)
173 |
--------------------------------------------------------------------------------
/Example Apps/iOS Example/Scenes/PresetsController.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 | import SparrowKit
24 | import SPDiffable
25 | import SPIndicator
26 |
27 | class PresetsController: SPDiffableTableController {
28 |
29 | // MARK: - Init
30 |
31 | init() {
32 | super.init(style: .insetGrouped)
33 | }
34 |
35 | required init?(coder: NSCoder) {
36 | fatalError("init(coder:) has not been implemented")
37 | }
38 |
39 | // MARK: - Lifecycle
40 |
41 | override func viewDidLoad() {
42 | super.viewDidLoad()
43 | navigationItem.title = "SPIndicator Presets"
44 |
45 | currentPreset = presets.first!
46 | configureDiffable(sections: content, cellProviders: SPDiffableTableDataSource.CellProvider.default)
47 |
48 | let segmentedControl = UISegmentedControl(items: ["Top", "Center", "Bottom"])
49 | navigationItem.titleView = segmentedControl
50 | segmentedControl.selectedSegmentIndex = 0
51 |
52 | navigationController?.isToolbarHidden = false
53 | toolbarItems = [
54 | .init(image: .system("chevron.down.circle"), primaryAction: .init(handler: { (action) in
55 | guard let currentPreset = self.currentPreset else {
56 | self.currentPreset = self.presets.first
57 | return
58 | }
59 | guard let index = self.presets.firstIndex(where: { $0.id == currentPreset.id }) else { return }
60 | self.currentPreset = self.presets[safe: index + 1]
61 | if self.currentPreset == nil {
62 | self.currentPreset = self.presets.first
63 | }
64 | })),
65 | .init(barButtonSystemItem: .flexibleSpace, target: nil, action: nil),
66 | .init(systemItem: .play, primaryAction: .init(handler: { [weak self] (action) in
67 | guard let self = self else { return }
68 | guard let preset = self.currentPreset else { return }
69 | guard let segmentControl = self.navigationItem.titleView as? UISegmentedControl else { return }
70 |
71 | let presentSide: SPIndicatorPresentSide = {
72 | switch segmentControl.selectedSegmentIndex {
73 | case 0: return SPIndicatorPresentSide.top
74 | case 1: return SPIndicatorPresentSide.center
75 | case 2: return SPIndicatorPresentSide.bottom
76 | default: fatalError()
77 | }
78 | }()
79 |
80 | if let iconPreset = preset.preset {
81 | SPIndicator.present(title: preset.title, message: preset.message, preset: iconPreset, from: presentSide, completion: nil)
82 | } else {
83 | SPIndicator.present(title: preset.title, message: preset.message, haptic: .success, from: presentSide)
84 | }
85 |
86 | }), menu: nil),
87 | .init(barButtonSystemItem: .flexibleSpace, target: nil, action: nil),
88 | ]
89 | }
90 |
91 | // MARK: - Data
92 |
93 | fileprivate var presets: [IndicatorPresetModel] {
94 | return [
95 | IndicatorPresetModel(
96 | name: "Done",
97 | title: "Done",
98 | message: "Animatable",
99 | preset: .done
100 | ),
101 | IndicatorPresetModel(
102 | name: "Error",
103 | title: "Oops",
104 | message: "Try again",
105 | preset: .error
106 | ),
107 | IndicatorPresetModel(
108 | name: "Spin",
109 | title: "Spin",
110 | message: "Animatable",
111 | preset: .spin(.medium)
112 | ),
113 | IndicatorPresetModel(
114 | name: "Custom Image",
115 | title: "Custom Image",
116 | message: "SFSymbols Image",
117 | preset: .custom(UIImage.init(systemName: "swift")!)
118 | ),
119 | IndicatorPresetModel(
120 | name: "Specific Tint",
121 | title: "Specific Tint",
122 | message: "For any image",
123 | preset: .custom(UIImage.init(systemName: "sun.min.fill")!.withTintColor(.systemYellow, renderingMode: .alwaysOriginal))
124 | ),
125 | IndicatorPresetModel(
126 | name: "Message",
127 | title: "Only Title",
128 | message: nil,
129 | preset: nil
130 | ),
131 | IndicatorPresetModel(
132 | name: "Message with Subtitle",
133 | title: "Title",
134 | message: "Subtitle",
135 | preset: nil
136 | ),
137 | IndicatorPresetModel(
138 | name: "Message Large Text with Icon",
139 | title: "You can read it later when you have time.",
140 | message: nil,
141 | preset: .custom(UIImage.init(systemName: "envelope.open.fill")!)
142 | ),
143 | IndicatorPresetModel(
144 | name: "Message Large Text without Icon",
145 | title: "You can read it later when you have time. Saved to Bookmarks",
146 | message: nil,
147 | preset: nil
148 | )
149 | ]
150 | }
151 |
152 | // MARK: - Diffable
153 |
154 | var currentPreset: IndicatorPresetModel? {
155 | didSet {
156 | diffableDataSource?.set(self.content, animated: true, completion: nil)
157 | }
158 | }
159 |
160 | var content: [SPDiffableSection] {
161 | let items = presets.map { (preset) -> SPDiffableTableRow in
162 | return SPDiffableTableRow(
163 | text: preset.name,
164 | accessoryType: (preset.id == currentPreset?.id) ? .checkmark : .none,
165 | selectionStyle: .none) { [weak self] _, _ in
166 | guard let self = self else { return }
167 | self.currentPreset = preset
168 | }
169 | }
170 | return [
171 | SPDiffableSection(id: "presets", header: nil, footer: nil, items: items)
172 | ]
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/Example Apps/SPIndicator.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | F43F829D26578DCA001D9B3D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F43F829426578DCA001D9B3D /* Assets.xcassets */; };
11 | F43F829E26578DCA001D9B3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F43F829526578DCA001D9B3D /* LaunchScreen.storyboard */; };
12 | F43F829F26578DCA001D9B3D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F43F829726578DCA001D9B3D /* AppDelegate.swift */; };
13 | F43F82A126578DCA001D9B3D /* PresetsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F43F829B26578DCA001D9B3D /* PresetsController.swift */; };
14 | F457C6AB276927C1005B4E19 /* AppImport in Frameworks */ = {isa = PBXBuildFile; productRef = F457C6AA276927C1005B4E19 /* AppImport */; };
15 | F457C6AD27692A1B005B4E19 /* AppImport in Frameworks */ = {isa = PBXBuildFile; productRef = F457C6AC27692A1B005B4E19 /* AppImport */; };
16 | F4B96F9C265AC36F00A01A29 /* IndicatorPresetModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4B96F9B265AC36F00A01A29 /* IndicatorPresetModel.swift */; };
17 | F4BAA4402658297300DA5BF7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4BAA43D2658297300DA5BF7 /* LaunchScreen.storyboard */; };
18 | F4BAA4412658297300DA5BF7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4BAA43E2658297300DA5BF7 /* Assets.xcassets */; };
19 | F4BAA4422658297300DA5BF7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4BAA43F2658297300DA5BF7 /* AppDelegate.swift */; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXCopyFilesBuildPhase section */
23 | F43F82EA26578F24001D9B3D /* Embed Watch Content */ = {
24 | isa = PBXCopyFilesBuildPhase;
25 | buildActionMask = 2147483647;
26 | dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
27 | dstSubfolderSpec = 16;
28 | files = (
29 | );
30 | name = "Embed Watch Content";
31 | runOnlyForDeploymentPostprocessing = 0;
32 | };
33 | /* End PBXCopyFilesBuildPhase section */
34 |
35 | /* Begin PBXFileReference section */
36 | F43F827B26578D83001D9B3D /* iOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
37 | F43F829426578DCA001D9B3D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
38 | F43F829626578DCA001D9B3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
39 | F43F829726578DCA001D9B3D /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
40 | F43F829B26578DCA001D9B3D /* PresetsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresetsController.swift; sourceTree = ""; };
41 | F43F829C26578DCA001D9B3D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
42 | F43F82AB26578F03001D9B3D /* tvOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "tvOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
43 | F43F82B926578F04001D9B3D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
44 | F457C6A92769270D005B4E19 /* AppImport */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = AppImport; sourceTree = ""; };
45 | F46C34BC27071E43005681BA /* SPIndicator */ = {isa = PBXFileReference; lastKnownFileType = folder; name = SPIndicator; path = ..; sourceTree = ""; };
46 | F4B96F9B265AC36F00A01A29 /* IndicatorPresetModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndicatorPresetModel.swift; sourceTree = ""; };
47 | F4BAA43D2658297300DA5BF7 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; };
48 | F4BAA43E2658297300DA5BF7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
49 | F4BAA43F2658297300DA5BF7 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
50 | /* End PBXFileReference section */
51 |
52 | /* Begin PBXFrameworksBuildPhase section */
53 | F43F827826578D83001D9B3D /* Frameworks */ = {
54 | isa = PBXFrameworksBuildPhase;
55 | buildActionMask = 2147483647;
56 | files = (
57 | F457C6AB276927C1005B4E19 /* AppImport in Frameworks */,
58 | );
59 | runOnlyForDeploymentPostprocessing = 0;
60 | };
61 | F43F82A826578F03001D9B3D /* Frameworks */ = {
62 | isa = PBXFrameworksBuildPhase;
63 | buildActionMask = 2147483647;
64 | files = (
65 | F457C6AD27692A1B005B4E19 /* AppImport in Frameworks */,
66 | );
67 | runOnlyForDeploymentPostprocessing = 0;
68 | };
69 | /* End PBXFrameworksBuildPhase section */
70 |
71 | /* Begin PBXGroup section */
72 | F43F827226578D83001D9B3D = {
73 | isa = PBXGroup;
74 | children = (
75 | F457C6A92769270D005B4E19 /* AppImport */,
76 | F46C34BC27071E43005681BA /* SPIndicator */,
77 | F43F829226578DCA001D9B3D /* iOS Example */,
78 | F43F82AC26578F03001D9B3D /* tvOS Example */,
79 | F43F827C26578D83001D9B3D /* Products */,
80 | F43F82A426578E31001D9B3D /* Frameworks */,
81 | );
82 | sourceTree = "";
83 | };
84 | F43F827C26578D83001D9B3D /* Products */ = {
85 | isa = PBXGroup;
86 | children = (
87 | F43F827B26578D83001D9B3D /* iOS Example.app */,
88 | F43F82AB26578F03001D9B3D /* tvOS Example.app */,
89 | );
90 | name = Products;
91 | sourceTree = "";
92 | };
93 | F43F829226578DCA001D9B3D /* iOS Example */ = {
94 | isa = PBXGroup;
95 | children = (
96 | F43F829326578DCA001D9B3D /* App */,
97 | F4B96F9A265AC36800A01A29 /* Models */,
98 | F43F829A26578DCA001D9B3D /* Scenes */,
99 | F43F829C26578DCA001D9B3D /* Info.plist */,
100 | );
101 | path = "iOS Example";
102 | sourceTree = "";
103 | };
104 | F43F829326578DCA001D9B3D /* App */ = {
105 | isa = PBXGroup;
106 | children = (
107 | F43F829726578DCA001D9B3D /* AppDelegate.swift */,
108 | F43F829426578DCA001D9B3D /* Assets.xcassets */,
109 | F43F829526578DCA001D9B3D /* LaunchScreen.storyboard */,
110 | );
111 | path = App;
112 | sourceTree = "";
113 | };
114 | F43F829A26578DCA001D9B3D /* Scenes */ = {
115 | isa = PBXGroup;
116 | children = (
117 | F43F829B26578DCA001D9B3D /* PresetsController.swift */,
118 | );
119 | path = Scenes;
120 | sourceTree = "";
121 | };
122 | F43F82A426578E31001D9B3D /* Frameworks */ = {
123 | isa = PBXGroup;
124 | children = (
125 | );
126 | name = Frameworks;
127 | sourceTree = "";
128 | };
129 | F43F82AC26578F03001D9B3D /* tvOS Example */ = {
130 | isa = PBXGroup;
131 | children = (
132 | F4BAA43C2658297300DA5BF7 /* App */,
133 | F43F82B926578F04001D9B3D /* Info.plist */,
134 | );
135 | path = "tvOS Example";
136 | sourceTree = "";
137 | };
138 | F4B96F9A265AC36800A01A29 /* Models */ = {
139 | isa = PBXGroup;
140 | children = (
141 | F4B96F9B265AC36F00A01A29 /* IndicatorPresetModel.swift */,
142 | );
143 | path = Models;
144 | sourceTree = "";
145 | };
146 | F4BAA43C2658297300DA5BF7 /* App */ = {
147 | isa = PBXGroup;
148 | children = (
149 | F4BAA43F2658297300DA5BF7 /* AppDelegate.swift */,
150 | F4BAA43D2658297300DA5BF7 /* LaunchScreen.storyboard */,
151 | F4BAA43E2658297300DA5BF7 /* Assets.xcassets */,
152 | );
153 | path = App;
154 | sourceTree = "";
155 | };
156 | /* End PBXGroup section */
157 |
158 | /* Begin PBXNativeTarget section */
159 | F43F827A26578D83001D9B3D /* iOS Example */ = {
160 | isa = PBXNativeTarget;
161 | buildConfigurationList = F43F828F26578D84001D9B3D /* Build configuration list for PBXNativeTarget "iOS Example" */;
162 | buildPhases = (
163 | F43F827726578D83001D9B3D /* Sources */,
164 | F43F827826578D83001D9B3D /* Frameworks */,
165 | F43F827926578D83001D9B3D /* Resources */,
166 | F43F82EA26578F24001D9B3D /* Embed Watch Content */,
167 | );
168 | buildRules = (
169 | );
170 | dependencies = (
171 | );
172 | name = "iOS Example";
173 | packageProductDependencies = (
174 | F457C6AA276927C1005B4E19 /* AppImport */,
175 | );
176 | productName = SparrowKit;
177 | productReference = F43F827B26578D83001D9B3D /* iOS Example.app */;
178 | productType = "com.apple.product-type.application";
179 | };
180 | F43F82AA26578F03001D9B3D /* tvOS Example */ = {
181 | isa = PBXNativeTarget;
182 | buildConfigurationList = F43F82BA26578F04001D9B3D /* Build configuration list for PBXNativeTarget "tvOS Example" */;
183 | buildPhases = (
184 | F43F82A726578F03001D9B3D /* Sources */,
185 | F43F82A826578F03001D9B3D /* Frameworks */,
186 | F43F82A926578F03001D9B3D /* Resources */,
187 | );
188 | buildRules = (
189 | );
190 | dependencies = (
191 | );
192 | name = "tvOS Example";
193 | packageProductDependencies = (
194 | F457C6AC27692A1B005B4E19 /* AppImport */,
195 | );
196 | productName = "tvOS Example";
197 | productReference = F43F82AB26578F03001D9B3D /* tvOS Example.app */;
198 | productType = "com.apple.product-type.application";
199 | };
200 | /* End PBXNativeTarget section */
201 |
202 | /* Begin PBXProject section */
203 | F43F827326578D83001D9B3D /* Project object */ = {
204 | isa = PBXProject;
205 | attributes = {
206 | LastSwiftUpdateCheck = 1250;
207 | LastUpgradeCheck = 1250;
208 | TargetAttributes = {
209 | F43F827A26578D83001D9B3D = {
210 | CreatedOnToolsVersion = 12.5;
211 | };
212 | F43F82AA26578F03001D9B3D = {
213 | CreatedOnToolsVersion = 12.5;
214 | };
215 | };
216 | };
217 | buildConfigurationList = F43F827626578D83001D9B3D /* Build configuration list for PBXProject "SPIndicator" */;
218 | compatibilityVersion = "Xcode 9.3";
219 | developmentRegion = en;
220 | hasScannedForEncodings = 0;
221 | knownRegions = (
222 | en,
223 | Base,
224 | );
225 | mainGroup = F43F827226578D83001D9B3D;
226 | packageReferences = (
227 | );
228 | productRefGroup = F43F827C26578D83001D9B3D /* Products */;
229 | projectDirPath = "";
230 | projectRoot = "";
231 | targets = (
232 | F43F827A26578D83001D9B3D /* iOS Example */,
233 | F43F82AA26578F03001D9B3D /* tvOS Example */,
234 | );
235 | };
236 | /* End PBXProject section */
237 |
238 | /* Begin PBXResourcesBuildPhase section */
239 | F43F827926578D83001D9B3D /* Resources */ = {
240 | isa = PBXResourcesBuildPhase;
241 | buildActionMask = 2147483647;
242 | files = (
243 | F43F829D26578DCA001D9B3D /* Assets.xcassets in Resources */,
244 | F43F829E26578DCA001D9B3D /* LaunchScreen.storyboard in Resources */,
245 | );
246 | runOnlyForDeploymentPostprocessing = 0;
247 | };
248 | F43F82A926578F03001D9B3D /* Resources */ = {
249 | isa = PBXResourcesBuildPhase;
250 | buildActionMask = 2147483647;
251 | files = (
252 | F4BAA4412658297300DA5BF7 /* Assets.xcassets in Resources */,
253 | F4BAA4402658297300DA5BF7 /* LaunchScreen.storyboard in Resources */,
254 | );
255 | runOnlyForDeploymentPostprocessing = 0;
256 | };
257 | /* End PBXResourcesBuildPhase section */
258 |
259 | /* Begin PBXSourcesBuildPhase section */
260 | F43F827726578D83001D9B3D /* Sources */ = {
261 | isa = PBXSourcesBuildPhase;
262 | buildActionMask = 2147483647;
263 | files = (
264 | F43F82A126578DCA001D9B3D /* PresetsController.swift in Sources */,
265 | F4B96F9C265AC36F00A01A29 /* IndicatorPresetModel.swift in Sources */,
266 | F43F829F26578DCA001D9B3D /* AppDelegate.swift in Sources */,
267 | );
268 | runOnlyForDeploymentPostprocessing = 0;
269 | };
270 | F43F82A726578F03001D9B3D /* Sources */ = {
271 | isa = PBXSourcesBuildPhase;
272 | buildActionMask = 2147483647;
273 | files = (
274 | F4BAA4422658297300DA5BF7 /* AppDelegate.swift in Sources */,
275 | );
276 | runOnlyForDeploymentPostprocessing = 0;
277 | };
278 | /* End PBXSourcesBuildPhase section */
279 |
280 | /* Begin PBXVariantGroup section */
281 | F43F829526578DCA001D9B3D /* LaunchScreen.storyboard */ = {
282 | isa = PBXVariantGroup;
283 | children = (
284 | F43F829626578DCA001D9B3D /* Base */,
285 | );
286 | name = LaunchScreen.storyboard;
287 | sourceTree = "";
288 | };
289 | /* End PBXVariantGroup section */
290 |
291 | /* Begin XCBuildConfiguration section */
292 | F43F828D26578D84001D9B3D /* Debug */ = {
293 | isa = XCBuildConfiguration;
294 | buildSettings = {
295 | ALWAYS_SEARCH_USER_PATHS = NO;
296 | CLANG_ANALYZER_NONNULL = YES;
297 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
298 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
299 | CLANG_CXX_LIBRARY = "libc++";
300 | CLANG_ENABLE_MODULES = YES;
301 | CLANG_ENABLE_OBJC_ARC = YES;
302 | CLANG_ENABLE_OBJC_WEAK = YES;
303 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
304 | CLANG_WARN_BOOL_CONVERSION = YES;
305 | CLANG_WARN_COMMA = YES;
306 | CLANG_WARN_CONSTANT_CONVERSION = YES;
307 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
308 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
309 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
310 | CLANG_WARN_EMPTY_BODY = YES;
311 | CLANG_WARN_ENUM_CONVERSION = YES;
312 | CLANG_WARN_INFINITE_RECURSION = YES;
313 | CLANG_WARN_INT_CONVERSION = YES;
314 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
315 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
316 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
317 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
318 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
319 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
320 | CLANG_WARN_STRICT_PROTOTYPES = YES;
321 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
322 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
323 | CLANG_WARN_UNREACHABLE_CODE = YES;
324 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
325 | COPY_PHASE_STRIP = NO;
326 | DEBUG_INFORMATION_FORMAT = dwarf;
327 | ENABLE_STRICT_OBJC_MSGSEND = YES;
328 | ENABLE_TESTABILITY = YES;
329 | GCC_C_LANGUAGE_STANDARD = gnu11;
330 | GCC_DYNAMIC_NO_PIC = NO;
331 | GCC_NO_COMMON_BLOCKS = YES;
332 | GCC_OPTIMIZATION_LEVEL = 0;
333 | GCC_PREPROCESSOR_DEFINITIONS = (
334 | "DEBUG=1",
335 | "$(inherited)",
336 | );
337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
339 | GCC_WARN_UNDECLARED_SELECTOR = YES;
340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
341 | GCC_WARN_UNUSED_FUNCTION = YES;
342 | GCC_WARN_UNUSED_VARIABLE = YES;
343 | IPHONEOS_DEPLOYMENT_TARGET = 14.5;
344 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
345 | MTL_FAST_MATH = YES;
346 | ONLY_ACTIVE_ARCH = YES;
347 | SDKROOT = iphoneos;
348 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
349 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
350 | };
351 | name = Debug;
352 | };
353 | F43F828E26578D84001D9B3D /* Release */ = {
354 | isa = XCBuildConfiguration;
355 | buildSettings = {
356 | ALWAYS_SEARCH_USER_PATHS = NO;
357 | CLANG_ANALYZER_NONNULL = YES;
358 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
359 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
360 | CLANG_CXX_LIBRARY = "libc++";
361 | CLANG_ENABLE_MODULES = YES;
362 | CLANG_ENABLE_OBJC_ARC = YES;
363 | CLANG_ENABLE_OBJC_WEAK = YES;
364 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
365 | CLANG_WARN_BOOL_CONVERSION = YES;
366 | CLANG_WARN_COMMA = YES;
367 | CLANG_WARN_CONSTANT_CONVERSION = YES;
368 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
369 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
370 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
371 | CLANG_WARN_EMPTY_BODY = YES;
372 | CLANG_WARN_ENUM_CONVERSION = YES;
373 | CLANG_WARN_INFINITE_RECURSION = YES;
374 | CLANG_WARN_INT_CONVERSION = YES;
375 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
376 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
377 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
378 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
379 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
380 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
381 | CLANG_WARN_STRICT_PROTOTYPES = YES;
382 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
383 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
384 | CLANG_WARN_UNREACHABLE_CODE = YES;
385 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
386 | COPY_PHASE_STRIP = NO;
387 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
388 | ENABLE_NS_ASSERTIONS = NO;
389 | ENABLE_STRICT_OBJC_MSGSEND = YES;
390 | GCC_C_LANGUAGE_STANDARD = gnu11;
391 | GCC_NO_COMMON_BLOCKS = YES;
392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
394 | GCC_WARN_UNDECLARED_SELECTOR = YES;
395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
396 | GCC_WARN_UNUSED_FUNCTION = YES;
397 | GCC_WARN_UNUSED_VARIABLE = YES;
398 | IPHONEOS_DEPLOYMENT_TARGET = 14.5;
399 | MTL_ENABLE_DEBUG_INFO = NO;
400 | MTL_FAST_MATH = YES;
401 | SDKROOT = iphoneos;
402 | SWIFT_COMPILATION_MODE = wholemodule;
403 | SWIFT_OPTIMIZATION_LEVEL = "-O";
404 | VALIDATE_PRODUCT = YES;
405 | };
406 | name = Release;
407 | };
408 | F43F829026578D84001D9B3D /* Debug */ = {
409 | isa = XCBuildConfiguration;
410 | buildSettings = {
411 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
412 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "";
413 | CODE_SIGN_STYLE = Automatic;
414 | DEVELOPMENT_TEAM = NPYZ6A48AA;
415 | INFOPLIST_FILE = "iOS Example/Info.plist";
416 | IPHONEOS_DEPLOYMENT_TARGET = 14.1;
417 | LD_RUNPATH_SEARCH_PATHS = (
418 | "$(inherited)",
419 | "@executable_path/Frameworks",
420 | );
421 | PRODUCT_BUNDLE_IDENTIFIER = by.ivanvorobei.opensource.spindicator.ios;
422 | PRODUCT_NAME = "$(TARGET_NAME)";
423 | SWIFT_VERSION = 5.0;
424 | TARGETED_DEVICE_FAMILY = "1,2";
425 | };
426 | name = Debug;
427 | };
428 | F43F829126578D84001D9B3D /* Release */ = {
429 | isa = XCBuildConfiguration;
430 | buildSettings = {
431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
432 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "";
433 | CODE_SIGN_STYLE = Automatic;
434 | DEVELOPMENT_TEAM = NPYZ6A48AA;
435 | INFOPLIST_FILE = "iOS Example/Info.plist";
436 | IPHONEOS_DEPLOYMENT_TARGET = 14.1;
437 | LD_RUNPATH_SEARCH_PATHS = (
438 | "$(inherited)",
439 | "@executable_path/Frameworks",
440 | );
441 | PRODUCT_BUNDLE_IDENTIFIER = by.ivanvorobei.opensource.spindicator.ios;
442 | PRODUCT_NAME = "$(TARGET_NAME)";
443 | SWIFT_VERSION = 5.0;
444 | TARGETED_DEVICE_FAMILY = "1,2";
445 | };
446 | name = Release;
447 | };
448 | F43F82BB26578F04001D9B3D /* Debug */ = {
449 | isa = XCBuildConfiguration;
450 | buildSettings = {
451 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
452 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "";
453 | CODE_SIGN_STYLE = Automatic;
454 | DEVELOPMENT_TEAM = T9Z76HLJ3T;
455 | INFOPLIST_FILE = "tvOS Example/Info.plist";
456 | LD_RUNPATH_SEARCH_PATHS = (
457 | "$(inherited)",
458 | "@executable_path/Frameworks",
459 | );
460 | PRODUCT_BUNDLE_IDENTIFIER = by.ivanvorobei.opensource.spindicator.tvos;
461 | PRODUCT_NAME = "$(TARGET_NAME)";
462 | SDKROOT = appletvos;
463 | SWIFT_VERSION = 5.0;
464 | TARGETED_DEVICE_FAMILY = 3;
465 | TVOS_DEPLOYMENT_TARGET = 12.1;
466 | };
467 | name = Debug;
468 | };
469 | F43F82BC26578F04001D9B3D /* Release */ = {
470 | isa = XCBuildConfiguration;
471 | buildSettings = {
472 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
473 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = "";
474 | CODE_SIGN_STYLE = Automatic;
475 | DEVELOPMENT_TEAM = T9Z76HLJ3T;
476 | INFOPLIST_FILE = "tvOS Example/Info.plist";
477 | LD_RUNPATH_SEARCH_PATHS = (
478 | "$(inherited)",
479 | "@executable_path/Frameworks",
480 | );
481 | PRODUCT_BUNDLE_IDENTIFIER = by.ivanvorobei.opensource.spindicator.tvos;
482 | PRODUCT_NAME = "$(TARGET_NAME)";
483 | SDKROOT = appletvos;
484 | SWIFT_VERSION = 5.0;
485 | TARGETED_DEVICE_FAMILY = 3;
486 | TVOS_DEPLOYMENT_TARGET = 12.1;
487 | };
488 | name = Release;
489 | };
490 | /* End XCBuildConfiguration section */
491 |
492 | /* Begin XCConfigurationList section */
493 | F43F827626578D83001D9B3D /* Build configuration list for PBXProject "SPIndicator" */ = {
494 | isa = XCConfigurationList;
495 | buildConfigurations = (
496 | F43F828D26578D84001D9B3D /* Debug */,
497 | F43F828E26578D84001D9B3D /* Release */,
498 | );
499 | defaultConfigurationIsVisible = 0;
500 | defaultConfigurationName = Release;
501 | };
502 | F43F828F26578D84001D9B3D /* Build configuration list for PBXNativeTarget "iOS Example" */ = {
503 | isa = XCConfigurationList;
504 | buildConfigurations = (
505 | F43F829026578D84001D9B3D /* Debug */,
506 | F43F829126578D84001D9B3D /* Release */,
507 | );
508 | defaultConfigurationIsVisible = 0;
509 | defaultConfigurationName = Release;
510 | };
511 | F43F82BA26578F04001D9B3D /* Build configuration list for PBXNativeTarget "tvOS Example" */ = {
512 | isa = XCConfigurationList;
513 | buildConfigurations = (
514 | F43F82BB26578F04001D9B3D /* Debug */,
515 | F43F82BC26578F04001D9B3D /* Release */,
516 | );
517 | defaultConfigurationIsVisible = 0;
518 | defaultConfigurationName = Release;
519 | };
520 | /* End XCConfigurationList section */
521 |
522 | /* Begin XCSwiftPackageProductDependency section */
523 | F457C6AA276927C1005B4E19 /* AppImport */ = {
524 | isa = XCSwiftPackageProductDependency;
525 | productName = AppImport;
526 | };
527 | F457C6AC27692A1B005B4E19 /* AppImport */ = {
528 | isa = XCSwiftPackageProductDependency;
529 | productName = AppImport;
530 | };
531 | /* End XCSwiftPackageProductDependency section */
532 | };
533 | rootObject = F43F827326578D83001D9B3D /* Project object */;
534 | }
535 |
--------------------------------------------------------------------------------
/Sources/SPIndicator/SPIndicatorView.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | // Copyright © 2021 Ivan Vorobei (hello@ivanvorobei.io)
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 |
22 | import UIKit
23 |
24 | #if os(iOS)
25 |
26 | /**
27 | SPIndicator: Main view. Can be customisable if need.
28 |
29 | For change duration, check method `present` and pass duration and other specific property if need customise.
30 |
31 | Here available set window on which shoud be present.
32 | If you have some windows, you shoud configure it. Check property `presentWindow`.
33 |
34 | For disable dismiss by drag, check property `.dismissByDrag`.
35 |
36 | Recomended call `SPIndicator` and choose style func.
37 | */
38 | @available(iOSApplicationExtension, unavailable)
39 | open class SPIndicatorView: UIView {
40 |
41 | // MARK: - UIAppearance
42 |
43 | @objc dynamic open var duration: TimeInterval = 1.5
44 |
45 | // MARK: - Properties
46 |
47 | /**
48 | SPIndicator: Change it for set `top` or `bottom` present side.
49 | Shoud be change before present, instead of no effect.
50 | */
51 | open var presentSide: SPIndicatorPresentSide = .top
52 |
53 | /**
54 | SPIndicator: By default allow drag indicator for hide.
55 | While indicator is dragging, dismiss not work.
56 | This behaviar can be disabled.
57 | */
58 | open var dismissByDrag: Bool = true {
59 | didSet {
60 | setGesture()
61 | }
62 | }
63 |
64 | /**
65 | SPIndicator: Completion call after hide indicator.
66 | */
67 | open var completion: (() -> Void)? = nil
68 |
69 | // MARK: - Views
70 |
71 | open var titleLabel: UILabel?
72 | open var subtitleLabel: UILabel?
73 | open var iconView: UIView?
74 |
75 | private lazy var backgroundView: UIVisualEffectView = {
76 | let view: UIVisualEffectView = {
77 | if #available(iOS 13.0, *) {
78 | return UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
79 | } else {
80 | return UIVisualEffectView(effect: UIBlurEffect(style: .light))
81 | }
82 | }()
83 | view.isUserInteractionEnabled = false
84 | return view
85 | }()
86 |
87 | weak open var presentWindow: UIWindow?
88 |
89 | // MARK: - Init
90 |
91 | public init(title: String, message: String? = nil, preset: SPIndicatorIconPreset) {
92 | super.init(frame: CGRect.zero)
93 | commonInit()
94 | layout = SPIndicatorLayout(for: preset)
95 | setTitle(title)
96 | if let message = message {
97 | setMessage(message)
98 | }
99 | setIcon(for: preset)
100 | }
101 |
102 | public init(title: String, message: String?) {
103 | super.init(frame: CGRect.zero)
104 | titleAreaFactor = 1.8
105 | minimumAreaWidth = 100
106 | commonInit()
107 | layout = SPIndicatorLayout.message()
108 | setTitle(title)
109 | if let message = message {
110 | setMessage(message)
111 | }
112 | }
113 |
114 | public required init?(coder aDecoder: NSCoder) {
115 | self.presentSide = .top
116 | super.init(coder: aDecoder)
117 | commonInit()
118 | }
119 |
120 | private func commonInit() {
121 | preservesSuperviewLayoutMargins = false
122 | if #available(iOS 11.0, *) {
123 | insetsLayoutMarginsFromSafeArea = false
124 | }
125 |
126 | backgroundColor = .clear
127 | backgroundView.layer.masksToBounds = true
128 | addSubview(backgroundView)
129 |
130 | setShadow()
131 | setGesture()
132 | }
133 |
134 | // MARK: - Configure
135 |
136 | private func setTitle(_ text: String) {
137 | let label = UILabel()
138 | label.font = UIFont.preferredFont(forTextStyle: .footnote, weight: .semibold, addPoints: 0)
139 | label.numberOfLines = 1
140 | let style = NSMutableParagraphStyle()
141 | style.lineBreakMode = .byTruncatingTail
142 | style.lineSpacing = 3
143 | label.attributedText = NSAttributedString(
144 | string: text, attributes: [.paragraphStyle: style]
145 | )
146 | label.textAlignment = .center
147 | label.textColor = UIColor.Compability.label.withAlphaComponent(0.6)
148 | titleLabel = label
149 | addSubview(label)
150 | }
151 |
152 | private func setMessage(_ text: String) {
153 | let label = UILabel()
154 | label.font = UIFont.preferredFont(forTextStyle: .footnote, weight: .semibold, addPoints: 0)
155 | label.numberOfLines = 1
156 | let style = NSMutableParagraphStyle()
157 | style.lineBreakMode = .byTruncatingTail
158 | style.lineSpacing = 2
159 | label.attributedText = NSAttributedString(
160 | string: text, attributes: [.paragraphStyle: style]
161 | )
162 | label.textAlignment = .center
163 | label.textColor = UIColor.Compability.label.withAlphaComponent(0.3)
164 | subtitleLabel = label
165 | addSubview(label)
166 | }
167 |
168 | private func setIcon(for preset: SPIndicatorIconPreset) {
169 | let view = preset.createView()
170 | self.iconView = view
171 | addSubview(view)
172 | }
173 |
174 | private func setShadow() {
175 | layer.shadowColor = UIColor.black.cgColor
176 | layer.shadowOpacity = 0.22
177 | layer.shadowOffset = .init(width: 0, height: 7)
178 | layer.shadowRadius = 40
179 |
180 | // Not use render shadow becouse backgorund is visual effect.
181 | // If turn on it, background will hide.
182 | // layer.shouldRasterize = true
183 | }
184 |
185 | private func setGesture() {
186 | if dismissByDrag {
187 | let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
188 | addGestureRecognizer(gestureRecognizer)
189 | self.gestureRecognizer = gestureRecognizer
190 | } else {
191 | self.gestureRecognizer = nil
192 | }
193 | }
194 |
195 | // MARK: - Present
196 |
197 | private var presentAndDismissDuration: TimeInterval = 0.6
198 |
199 | private var presentWithOpacity: Bool {
200 | if presentSide == .center { return true }
201 | return false
202 | }
203 |
204 | open func present(haptic: SPIndicatorHaptic = .success, completion: (() -> Void)? = nil) {
205 | present(duration: self.duration, haptic: haptic, completion: completion)
206 | }
207 |
208 | open func present(duration: TimeInterval, haptic: SPIndicatorHaptic = .success, completion: (() -> Void)? = nil) {
209 |
210 | if self.presentWindow == nil {
211 | self.presentWindow = UIApplication.shared.keyWindow
212 | }
213 |
214 | guard let window = self.presentWindow else { return }
215 |
216 | window.addSubview(self)
217 |
218 | // Prepare for present
219 |
220 | self.whenGestureEndShoudHide = false
221 | self.completion = completion
222 |
223 | isHidden = true
224 | sizeToFit()
225 | layoutSubviews()
226 | center.x = window.frame.midX
227 | toPresentPosition(.prepare(presentSide))
228 |
229 | self.alpha = presentWithOpacity ? 0 : 1
230 |
231 | // Present
232 |
233 | isHidden = false
234 | haptic.impact()
235 | UIView.animate(withDuration: presentAndDismissDuration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [.beginFromCurrentState, .curveEaseOut], animations: {
236 | self.toPresentPosition(.visible(self.presentSide))
237 | if self.presentWithOpacity { self.alpha = 1 }
238 | }, completion: { finished in
239 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration) {
240 | if self.gestureIsDragging {
241 | self.whenGestureEndShoudHide = true
242 | } else {
243 | self.dismiss()
244 | }
245 | }
246 | })
247 |
248 | if let iconView = self.iconView as? SPIndicatorIconAnimatable {
249 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + presentAndDismissDuration / 3) {
250 | iconView.animate()
251 | }
252 | }
253 | }
254 |
255 | @objc open func dismiss() {
256 | UIView.animate(withDuration: presentAndDismissDuration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [.beginFromCurrentState, .curveEaseIn], animations: {
257 | self.toPresentPosition(.prepare(self.presentSide))
258 | if self.presentWithOpacity { self.alpha = 0 }
259 | }, completion: { finished in
260 | self.removeFromSuperview()
261 | self.completion?()
262 | })
263 | }
264 |
265 | // MARK: - Internal
266 |
267 | private var minimumYTranslationForHideByGesture: CGFloat = -10
268 | private var maximumYTranslationByGesture: CGFloat = 60
269 |
270 | private var gestureRecognizer: UIPanGestureRecognizer?
271 | private var gestureIsDragging: Bool = false
272 | private var whenGestureEndShoudHide: Bool = false
273 |
274 | @objc func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
275 |
276 | if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
277 | self.gestureIsDragging = true
278 | let translation = gestureRecognizer.translation(in: self)
279 | let newTranslation: CGFloat = {
280 | switch presentSide {
281 | case .top:
282 | if translation.y <= 0 {
283 | return translation.y
284 | } else {
285 | return min(maximumYTranslationByGesture, translation.y.squareRoot())
286 | }
287 | case .bottom:
288 | if translation.y >= 0 {
289 | return translation.y
290 | } else {
291 | let absolute = abs(translation.y)
292 | return -min(maximumYTranslationByGesture, absolute.squareRoot())
293 | }
294 | case .center:
295 | let absolute = abs(translation.y).squareRoot()
296 | let newValue = translation.y < 0 ? -absolute : absolute
297 | return min(maximumYTranslationByGesture, newValue)
298 | }
299 | }()
300 | toPresentPosition(.fromVisible(newTranslation, from: (presentSide)))
301 | }
302 |
303 | if gestureRecognizer.state == .ended {
304 | gestureIsDragging = false
305 |
306 | var shoudDismissWhenEndAnimation: Bool = false
307 |
308 | UIView.animate(withDuration: presentAndDismissDuration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [.beginFromCurrentState, .curveEaseIn], animations: {
309 | if self.whenGestureEndShoudHide {
310 | self.toPresentPosition(.prepare(self.presentSide))
311 | shoudDismissWhenEndAnimation = true
312 | } else {
313 | let translation = gestureRecognizer.translation(in: self)
314 | if translation.y < self.minimumYTranslationForHideByGesture {
315 | self.toPresentPosition(.prepare(self.presentSide))
316 | shoudDismissWhenEndAnimation = true
317 | } else {
318 | self.toPresentPosition(.visible(self.presentSide))
319 | }
320 | }
321 | }, completion: { _ in
322 | if shoudDismissWhenEndAnimation {
323 | self.dismiss()
324 | }
325 | })
326 | }
327 | }
328 |
329 | private func toPresentPosition(_ position: PresentPosition) {
330 |
331 | let getPrepareTransform: ((_ side: SPIndicatorPresentSide) -> CGAffineTransform) = { [weak self] side in
332 | guard let self = self else { return .identity }
333 | guard let window = UIApplication.shared.windows.first else { return .identity }
334 | switch side {
335 | case .top:
336 | let topInset = window.safeAreaInsets.top
337 | let position = -(topInset + 50)
338 | return CGAffineTransform.identity.translatedBy(x: 0, y: position)
339 | case .bottom:
340 | let height = window.frame.height
341 | let bottomInset = window.safeAreaInsets.bottom
342 | let position = height + bottomInset + 50
343 | return CGAffineTransform.identity.translatedBy(x: 0, y: position)
344 | case .center:
345 | return CGAffineTransform.identity.translatedBy(x: 0, y: window.frame.height / 2 - self.frame.height / 2).scaledBy(x: 0.9, y: 0.9)
346 | }
347 | }
348 |
349 | let getVisibleTransform: ((_ side: SPIndicatorPresentSide) -> CGAffineTransform) = { [weak self] side in
350 | guard let self = self else { return .identity }
351 | guard let window = UIApplication.shared.windows.first else { return .identity }
352 | switch side {
353 | case .top:
354 | var topSafeAreaInsets = window.safeAreaInsets.top
355 | if topSafeAreaInsets < 20 { topSafeAreaInsets = 20 }
356 | let position = topSafeAreaInsets - 3 + self.offset
357 | return CGAffineTransform.identity.translatedBy(x: 0, y: position)
358 | case .bottom:
359 | let height = window.frame.height
360 | var bottomSafeAreaInsets = window.safeAreaInsets.top
361 | if bottomSafeAreaInsets < 20 { bottomSafeAreaInsets = 20 }
362 | let position = height - bottomSafeAreaInsets - 3 - self.frame.height - self.offset
363 | return CGAffineTransform.identity.translatedBy(x: 0, y: position)
364 | case .center:
365 | return CGAffineTransform.identity.translatedBy(x: 0, y: window.frame.height / 2 - self.frame.height / 2)
366 | }
367 | }
368 |
369 | switch position {
370 | case .prepare(let presentSide):
371 | transform = getPrepareTransform(presentSide)
372 | case .visible(let presentSide):
373 | transform = getVisibleTransform(presentSide)
374 | case .fromVisible(let translation, let presentSide):
375 | transform = getVisibleTransform(presentSide).translatedBy(x: 0, y: translation)
376 | }
377 | }
378 |
379 | // MARK: - Layout
380 |
381 | /**
382 | SPIndicator: Wraper of layout values.
383 | */
384 | open var layout: SPIndicatorLayout = .init()
385 |
386 | /**
387 | SPIndicator: Alert offset
388 | */
389 | open var offset: CGFloat = 0
390 |
391 | private var areaHeight: CGFloat = 50
392 | private var minimumAreaWidth: CGFloat = 196
393 | private var maximumAreaWidth: CGFloat = 260
394 | private var titleAreaFactor: CGFloat = 2.5
395 | private var spaceBetweenTitles: CGFloat = 1
396 | private var spaceBetweenTitlesAndImage: CGFloat = 16
397 |
398 | private var titlesCompactWidth: CGFloat {
399 | if let iconView = self.iconView {
400 | let space = iconView.frame.maxY + spaceBetweenTitlesAndImage
401 | return frame.width - space * 2
402 | } else {
403 | return frame.width - layoutMargins.left - layoutMargins.right
404 | }
405 | }
406 |
407 | private var titlesFullWidth: CGFloat {
408 | if let iconView = self.iconView {
409 | let space = iconView.frame.maxY + spaceBetweenTitlesAndImage
410 | return frame.width - space - layoutMargins.right - self.spaceBetweenTitlesAndImage
411 | } else {
412 | return frame.width - layoutMargins.left - layoutMargins.right
413 | }
414 | }
415 |
416 | open override func sizeThatFits(_ size: CGSize) -> CGSize {
417 | titleLabel?.sizeToFit()
418 | let titleWidth: CGFloat = titleLabel?.frame.width ?? 0
419 | subtitleLabel?.sizeToFit()
420 | let subtitleWidth: CGFloat = subtitleLabel?.frame.width ?? 0
421 | var width = (max(titleWidth, subtitleWidth) * titleAreaFactor).rounded()
422 |
423 | if width < minimumAreaWidth { width = minimumAreaWidth }
424 | if width > maximumAreaWidth { width = maximumAreaWidth }
425 |
426 | return .init(width: width, height: areaHeight)
427 | }
428 |
429 | open override func layoutSubviews() {
430 | super.layoutSubviews()
431 |
432 | layoutMargins = layout.margins
433 | layer.cornerRadius = frame.height / 2
434 | backgroundView.frame = bounds
435 | backgroundView.layer.cornerRadius = layer.cornerRadius
436 |
437 | // Flags
438 |
439 | let hasIcon = (self.iconView != nil)
440 | let hasTitle = (self.titleLabel != nil)
441 | let hasSubtite = (self.subtitleLabel != nil)
442 |
443 | let fitTitleToCompact: Bool = {
444 | guard let titleLabel = self.titleLabel else { return true }
445 | titleLabel.numberOfLines = 1
446 | titleLabel.sizeToFit()
447 | return titleLabel.frame.width < titlesCompactWidth
448 | }()
449 |
450 | let fitSubtitleToCompact: Bool = {
451 | guard let subtitleLabel = self.subtitleLabel else { return true }
452 | subtitleLabel.numberOfLines = 1
453 | subtitleLabel.sizeToFit()
454 | return subtitleLabel.frame.width < titlesCompactWidth
455 | }()
456 |
457 | let notFitAnyLabelToCompact: Bool = {
458 | if !fitTitleToCompact { return true }
459 | if !fitSubtitleToCompact { return true }
460 | return false
461 | }()
462 |
463 | var layout: LayoutGrid = .iconTitleCentered
464 |
465 | if (hasIcon && hasTitle && hasSubtite) && !notFitAnyLabelToCompact {
466 | layout = .iconTitleMessageCentered
467 | }
468 |
469 | if (hasIcon && hasTitle && hasSubtite) && notFitAnyLabelToCompact {
470 | layout = .iconTitleMessageLeading
471 | }
472 |
473 | if (hasIcon && hasTitle && !hasSubtite) {
474 | layout = .iconTitleCentered
475 | }
476 |
477 | if (!hasIcon && hasTitle && !hasSubtite) {
478 | layout = .title
479 | }
480 |
481 | if (!hasIcon && hasTitle && hasSubtite) {
482 | layout = .titleMessage
483 | }
484 |
485 | // Actions
486 |
487 | let layoutIcon = { [weak self] in
488 | guard let self = self else { return }
489 | guard let iconView = self.iconView else { return }
490 | iconView.frame = .init(
491 | origin: .init(x: self.layoutMargins.left, y: iconView.frame.origin.y),
492 | size: self.layout.iconSize
493 | )
494 | iconView.center.y = self.bounds.midY
495 | }
496 |
497 | let layoutTitleCenteredCompact = { [weak self] in
498 | guard let self = self else { return }
499 | guard let titleLabel = self.titleLabel else { return }
500 | titleLabel.textAlignment = .center
501 | titleLabel.layoutDynamicHeight(width: self.titlesCompactWidth)
502 | titleLabel.center.x = self.frame.width / 2
503 | }
504 |
505 | let layoutTitleCenteredFullWidth = { [weak self] in
506 | guard let self = self else { return }
507 | guard let titleLabel = self.titleLabel else { return }
508 | titleLabel.textAlignment = .center
509 | titleLabel.layoutDynamicHeight(width: self.titlesFullWidth)
510 | titleLabel.center.x = self.frame.width / 2
511 | }
512 |
513 | let layoutTitleLeadingFullWidth = { [weak self] in
514 | guard let self = self else { return }
515 | guard let titleLabel = self.titleLabel else { return }
516 | guard let iconView = self.iconView else { return }
517 | let rtl = self.effectiveUserInterfaceLayoutDirection == .rightToLeft
518 | titleLabel.textAlignment = rtl ? .right : .left
519 | titleLabel.layoutDynamicHeight(width: self.titlesFullWidth)
520 | titleLabel.frame.origin.x = self.layoutMargins.left + iconView.frame.width + self.spaceBetweenTitlesAndImage
521 | }
522 |
523 | let layoutSubtitle = { [weak self] in
524 | guard let self = self else { return }
525 | guard let titleLabel = self.titleLabel else { return }
526 | guard let subtitleLabel = self.subtitleLabel else { return }
527 | subtitleLabel.textAlignment = titleLabel.textAlignment
528 | subtitleLabel.layoutDynamicHeight(width: titleLabel.frame.width)
529 | subtitleLabel.frame.origin.x = titleLabel.frame.origin.x
530 | }
531 |
532 | let layoutTitleSubtitleByVertical = { [weak self] in
533 | guard let self = self else { return }
534 | guard let titleLabel = self.titleLabel else { return }
535 | guard let subtitleLabel = self.subtitleLabel else {
536 | titleLabel.center.y = self.bounds.midY
537 | return
538 | }
539 | let allHeight = titleLabel.frame.height + subtitleLabel.frame.height + self.spaceBetweenTitles
540 | titleLabel.frame.origin.y = (self.frame.height - allHeight) / 2
541 | subtitleLabel.frame.origin.y = titleLabel.frame.maxY + self.spaceBetweenTitles
542 | }
543 |
544 | // Apply
545 |
546 | switch layout {
547 | case .iconTitleMessageCentered:
548 | layoutIcon()
549 | layoutTitleCenteredCompact()
550 | layoutSubtitle()
551 | case .iconTitleMessageLeading:
552 | layoutIcon()
553 | layoutTitleLeadingFullWidth()
554 | layoutSubtitle()
555 | case .iconTitleCentered:
556 | layoutIcon()
557 | titleLabel?.numberOfLines = 2
558 | layoutTitleCenteredCompact()
559 | case .iconTitleLeading:
560 | layoutIcon()
561 | titleLabel?.numberOfLines = 2
562 | layoutTitleLeadingFullWidth()
563 | case .title:
564 | titleLabel?.numberOfLines = 2
565 | layoutTitleCenteredFullWidth()
566 | case .titleMessage:
567 | layoutTitleCenteredFullWidth()
568 | layoutSubtitle()
569 | }
570 |
571 | layoutTitleSubtitleByVertical()
572 | }
573 |
574 | // MARK: - Models
575 |
576 | enum PresentPosition {
577 |
578 | case prepare(_ from: SPIndicatorPresentSide)
579 | case visible(_ from: SPIndicatorPresentSide)
580 | case fromVisible(_ translation: CGFloat, from: SPIndicatorPresentSide)
581 | }
582 |
583 | enum LayoutGrid {
584 |
585 | case iconTitleMessageCentered
586 | case iconTitleMessageLeading
587 | case iconTitleCentered
588 | case iconTitleLeading
589 | case title
590 | case titleMessage
591 | }
592 | }
593 |
594 | #endif
595 |
--------------------------------------------------------------------------------