├── version.txt
├── Images
├── demo.png
├── logo.png
├── demo-bilateral.png
└── demo-unilateral.png
├── Example
├── InchwormExample
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── iTunesArtwork@1x.png
│ │ ├── iTunesArtwork@2x.png
│ │ ├── iTunesArtwork@3x.png
│ │ ├── settings.imageset
│ │ │ ├── settings@2x.png
│ │ │ ├── settings@3x.png
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── ItunesArtwork@2x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ └── Contents.json
│ │ ├── ic_flash_on.imageset
│ │ │ ├── ic_flash_on@2x.png
│ │ │ ├── ic_flash_on@3x.png
│ │ │ └── Contents.json
│ │ └── ic_camera_front.imageset
│ │ │ ├── ic_camera_front@2x.png
│ │ │ ├── ic_camera_front@3x.png
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── SceneDelegate.swift
│ └── ViewController.swift
├── InchwormExample.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── project.pbxproj
├── InchwormExample.xcworkspace
│ ├── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── contents.xcworkspacedata
└── Podfile
├── Inchworm.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── .github
└── workflows
│ └── main.yml
├── Inchworm
├── Inchworm.h
├── Info.plist
└── Source
│ ├── Inchworm.swift
│ ├── ProcessIndicatorViewModel.swift
│ ├── SlideRulerPositionHelper.swift
│ ├── ProcessIndicatorContainer.swift
│ ├── Slider.swift
│ ├── SlideRuler.swift
│ └── ProcessIndicatorView.swift
├── InchwormTests
├── Info.plist
└── InchwormTests.swift
├── LICENSE
├── Inchworm.podspec
├── .gitignore
├── README.md
└── CHANGELOG.md
/version.txt:
--------------------------------------------------------------------------------
1 | 1.1.0
2 |
--------------------------------------------------------------------------------
/Images/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Images/demo.png
--------------------------------------------------------------------------------
/Images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Images/logo.png
--------------------------------------------------------------------------------
/Images/demo-bilateral.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Images/demo-bilateral.png
--------------------------------------------------------------------------------
/Images/demo-unilateral.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Images/demo-unilateral.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/iTunesArtwork@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/iTunesArtwork@1x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/iTunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/iTunesArtwork@2x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/iTunesArtwork@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/iTunesArtwork@3x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/settings.imageset/settings@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/settings.imageset/settings@2x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/settings.imageset/settings@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/settings.imageset/settings@3x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/ic_flash_on.imageset/ic_flash_on@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/ic_flash_on.imageset/ic_flash_on@2x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/ic_flash_on.imageset/ic_flash_on@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/ic_flash_on.imageset/ic_flash_on@3x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/ic_camera_front.imageset/ic_camera_front@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/ic_camera_front.imageset/ic_camera_front@2x.png
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/ic_camera_front.imageset/ic_camera_front@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guoyingtao/Inchworm/HEAD/Example/InchwormExample/Assets.xcassets/ic_camera_front.imageset/ic_camera_front@3x.png
--------------------------------------------------------------------------------
/Inchworm.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/InchwormExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/InchwormExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/InchwormExample.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Inchworm.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/InchwormExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'InchwormExample' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for InchwormExample
9 | pod 'Inchworm', :path => '../'
10 | # pod 'Inchworm'
11 | end
12 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: release-please
2 | on:
3 | push:
4 | branches:
5 | - master
6 | jobs:
7 | release-please:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: GoogleCloudPlatform/release-please-action@v2
11 | with:
12 | token: ${{ secrets.GITHUB_TOKEN }}
13 | release-type: simple
14 | version-file: "./version.txt"
15 | package-name: release-please-action
16 | default-branch: master
17 |
--------------------------------------------------------------------------------
/Inchworm/Inchworm.h:
--------------------------------------------------------------------------------
1 | //
2 | // Inchworm.h
3 | // Inchworm
4 | //
5 | // Created by Echo on 10/16/19.
6 | // Copyright © 2019 Echo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for Inchworm.
12 | FOUNDATION_EXPORT double InchwormVersionNumber;
13 |
14 | //! Project version string for Inchworm.
15 | FOUNDATION_EXPORT const unsigned char InchwormVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/settings.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "settings@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "settings@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/ic_flash_on.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_flash_on@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "ic_flash_on@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/ic_camera_front.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ic_camera_front@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "ic_camera_front@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
--------------------------------------------------------------------------------
/InchwormTests/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 |
22 |
23 |
--------------------------------------------------------------------------------
/Inchworm/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 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/InchwormTests/InchwormTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InchwormTests.swift
3 | // InchwormTests
4 | //
5 | // Created by Echo on 10/16/19.
6 | // Copyright © 2019 Echo. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Inchworm
11 |
12 | class InchwormTests: XCTestCase {
13 |
14 | override func setUp() {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testExample() {
23 | // This is an example of a functional test case.
24 | // Use XCTAssert and related functions to verify your tests produce the correct results.
25 | }
26 |
27 | func testPerformanceExample() {
28 | // This is an example of a performance test case.
29 | self.measure {
30 | // Put the code you want to measure the time of here.
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Yingtao Guo
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 |
--------------------------------------------------------------------------------
/Inchworm.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint Inchworm.podspec' to ensure this is a
3 | # valid spec and to remove all comments including this before submitting the spec.
4 | #
5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = "Inchworm"
11 | s.version = "1.1.0"
12 | s.summary = "A swift slider-style value adjusting tool"
13 |
14 | s.description = <<-DESC
15 | A swift value adjusting tool which mimics the slider to set value in Photo.app in iOS 13
16 | DESC
17 |
18 | s.homepage = "https://github.com/guoyingtao/Inchworm"
19 | s.license = { :type => "MIT", :file => "LICENSE" }
20 | s.author = { "Yingtao Guo" => "guoyingtao@outlook.com" }
21 | s.social_media_url = "http://twitter.com/guoyingtao"
22 | s.platform = :ios
23 | s.swift_version = "5.0"
24 | s.ios.deployment_target = "11.0"
25 | s.source = { :git => "https://github.com/guoyingtao/Inchworm.git", :tag => "v#{s.version}" }
26 | s.source_files = "Inchworm/**/*.{h,swift}"
27 | #s.exclude_files = "Inchworm/**/Info.plist"
28 |
29 | # s.info_plist = {
30 | # "CFBundleIdentifier" => "com.echo.framework.Inchworm"
31 | #}
32 |
33 | end
34 |
35 |
--------------------------------------------------------------------------------
/Example/InchwormExample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // InchwormExample
4 | //
5 | // Created by Echo on 10/16/19.
6 | // Copyright © 2019 Echo. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | // MARK: UISceneSession Lifecycle
22 |
23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
24 | // Called when a new scene session is being created.
25 | // Use this method to select a configuration to create the new scene with.
26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
27 | }
28 |
29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
30 | // Called when the user discards a scene session.
31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
33 | }
34 |
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/Example/InchwormExample/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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | Pods/
50 |
51 | # Carthage
52 | #
53 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
54 | # Carthage/Checkouts
55 |
56 | Carthage/Build
57 |
58 | # fastlane
59 | #
60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61 | # screenshots whenever they are needed.
62 | # For more information about the recommended setup visit:
63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
64 |
65 | fastlane/report.xml
66 | fastlane/Preview.html
67 | fastlane/screenshots/**/*.png
68 | fastlane/test_output
69 | /Example/Podfile.lock
70 | .DS_Store
71 |
--------------------------------------------------------------------------------
/Inchworm/Source/Inchworm.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Inchworm.swift
3 | // Inchworm
4 | //
5 | // Created by Echo on 10/16/19.
6 | // Copyright © 2019 Echo. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public struct ProcessIndicatorModel {
12 | var normalIconImage: CGImage
13 | var dimmedIconImage: CGImage
14 | var sliderValueRangeType: SliderValueRangeType
15 |
16 | public init(sliderValueRangeType: SliderValueRangeType, normalIconImage: CGImage, dimmedIconImage: CGImage) {
17 | self.normalIconImage = normalIconImage
18 | self.dimmedIconImage = dimmedIconImage
19 | self.sliderValueRangeType = sliderValueRangeType
20 | }
21 | }
22 |
23 | /**
24 | You can set frame to CGRecte.zero if Autolayout is used
25 | */
26 | public func createSlider(config: Config = Config(),
27 | frame: CGRect,
28 | processIndicatorModels: [ProcessIndicatorModel],
29 | activeIndex: Int) -> Slider {
30 | return Slider(config: config,
31 | frame: frame,
32 | processIndicatorModels: processIndicatorModels,
33 | activeIndex: activeIndex)
34 | }
35 |
36 | public struct Config {
37 | public var orientation: SliderOrientation = .horizontal
38 | public var indicatorSpan: CGFloat = 50
39 | public var slideRulerSpan: CGFloat = 50
40 | public var spaceBetweenIndicatorAndSlideRule: CGFloat = 10
41 | public var forceAlignCenterFeedback = true
42 |
43 | public init() {}
44 | }
45 |
46 | public enum SliderOrientation {
47 | case horizontal
48 | case vertical
49 | }
50 |
51 | public enum SliderValueRangeType {
52 | case bilateral(limit: Int, defaultValue: Int = 0)
53 | case unilateral(limit: Int, defaultValue: Int = 0)
54 | }
55 |
--------------------------------------------------------------------------------
/Inchworm/Source/ProcessIndicatorViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProcessIndicatorViewModel.swift
3 | // Inchworm
4 | //
5 | // Created by Yingtao Guo on 6/21/23.
6 | //
7 |
8 | import Foundation
9 |
10 | class ProcessIndicatorViewModel {
11 | var sliderValueRangeType: SliderValueRangeType
12 |
13 | var progress: Float = 0 {
14 | didSet {
15 | let progressValue: Int
16 | switch sliderValueRangeType {
17 | case .bilateral(let limit, _):
18 | fallthrough
19 | case .unilateral(let limit, _):
20 | progressValue = Int(progress * Float(limit))
21 | }
22 | didSetProgress(progressValue)
23 | }
24 | }
25 |
26 | var didSetProgress: (_ progressValue: Int) -> Void = { _ in }
27 |
28 | var status: IndicatorStatus = .initial {
29 | didSet {
30 | didSetStatus(status)
31 | }
32 | }
33 |
34 | var didSetStatus: (_ status: IndicatorStatus) -> Void = { _ in }
35 |
36 | var isActive = false
37 |
38 | init(sliderValueRangeType: SliderValueRangeType) {
39 | self.sliderValueRangeType = sliderValueRangeType
40 | }
41 |
42 | private func setDefaultProgress() {
43 | switch sliderValueRangeType {
44 | case .unilateral(let limit, let defaultValue):
45 | fallthrough
46 | case .bilateral(let limit, let defaultValue):
47 | progress = Float(defaultValue) / Float(limit)
48 | }
49 | }
50 |
51 | private func setInitialStatus() {
52 | if progress == 0 {
53 | status = .initial
54 | } else {
55 | status = .deactive
56 | }
57 | }
58 |
59 | func initialize() {
60 | setDefaultProgress()
61 | setInitialStatus()
62 | }
63 |
64 | func deactive() {
65 | isActive = false
66 |
67 | if status != .reset {
68 | if progress == 0 {
69 | status = .initial
70 | } else {
71 | status = .deactive
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Example/InchwormExample/Base.lproj/Main.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 |
33 |
34 |
--------------------------------------------------------------------------------
/Example/InchwormExample/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 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | # Inchworm
12 |
13 | A slider for adjusting values just like the one in Photo.app in iOS 13
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ## Requirements
22 | * iOS 11.0+
23 | * Xcode 10.0+
24 |
25 | ### CocoaPods
26 |
27 | ```ruby
28 | pod 'Inchworm', '~> 1.1.0'
29 | ```
30 |
31 | ## Usage
32 |
33 | * Create a Slider
34 |
35 | ``` swift
36 | let model1 = ProcessIndicatorModel(sliderValueRangeType: .unilateral(limit: 30, defaultValue: 10),
37 | normalIconImage: ,
38 | dimmedIconImage: )
39 |
40 | let model2 = ProcessIndicatorModel(sliderValueRangeType: .bilateral(limit: 40),
41 | normalIconImage: ,
42 | dimmedIconImage: )
43 |
44 | let model3 = ProcessIndicatorModel(sliderValueRangeType: .bilateral(limit: 50),
45 | normalIconImage: ,
46 | dimmedIconImage: )
47 |
48 |
49 | let modelList = [model1, model2, model3]
50 |
51 | let config = Inchworm.Config()
52 |
53 | let board = createSlider(config: config, frame: , processIndicatorModels: modelList, activeIndex: 1)
54 | ```
55 |
56 | * The caller needs to conform DialBoardDelegate
57 | ```swift
58 | public protocol SliderDelegate {
59 | func didGetOffsetRatio(_ slider: Inchworm.Slider, activeIndicatorIndex: Int, offsetRatio: Float)
60 | }
61 | ```
62 |
63 |
64 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [1.1.0](https://www.github.com/guoyingtao/Inchworm/compare/v1.0.0...v1.1.0) (2023-06-22)
4 |
5 |
6 | ### Features
7 |
8 | * support default value for ProcessIndicatorModel ([a8e45e4](https://www.github.com/guoyingtao/Inchworm/commit/a8e45e4f79643336ea4c65ec91ed8c0888fd68a1))
9 |
10 |
11 | ### Bug Fixes
12 |
13 | * fix a indicator UI update issue ([f5e049a](https://www.github.com/guoyingtao/Inchworm/commit/f5e049ab99299766eae46b41969658ca8b768627))
14 | * fix progress indicator container scroll issue ([c525bfd](https://www.github.com/guoyingtao/Inchworm/commit/c525bfd5881df0d84255787f6db6381c57d47ea7))
15 | * fix tapping indicator logic ([3272b80](https://www.github.com/guoyingtao/Inchworm/commit/3272b800e7f62290ab750a7ee61b53e04598724c))
16 | * improve scrolling indicator container logic ([04cfd7e](https://www.github.com/guoyingtao/Inchworm/commit/04cfd7edf99ff98bfa6fd8fe2af4e94244326c1c))
17 |
18 | ## 1.0.0 (2023-06-16)
19 |
20 |
21 | ### Features
22 |
23 | * add SliderZeroPositionType and SliderRulerPositionInfoProvider ([bc79bac](https://www.github.com/guoyingtao/Inchworm/commit/bc79bacf2a366a3e083a402d44036b6b276404f3))
24 | * improve the Example projects which includes three examples now ([df07ca6](https://www.github.com/guoyingtao/Inchworm/commit/df07ca618de1e4a3f03b74b3dbce3925f022ff44))
25 | * Improve UX when forcing align center ([a49ba58](https://www.github.com/guoyingtao/Inchworm/commit/a49ba58b04a2206174712f63dce71028cce0bb82))
26 | * support range value limit instead of only limit ([8722b6d](https://www.github.com/guoyingtao/Inchworm/commit/8722b6dcc98c30cba7a5159e91ad4551a97aad88))
27 |
28 |
29 | ### Bug Fixes
30 |
31 | * fix indicator colors ([c4d4b57](https://www.github.com/guoyingtao/Inchworm/commit/c4d4b57cd8de4a0085e0dd922b7cb5fbcc7a6a2c))
32 | * fix indicator status by adding editingOthers to IndicatorStatus ([d28e886](https://www.github.com/guoyingtao/Inchworm/commit/d28e886f1c72d1e00c1b92a4eb71e9fbb9834ee8))
33 | * fix the initial value error for sliderOffsetRatio ([22ab04f](https://www.github.com/guoyingtao/Inchworm/commit/22ab04f84f7e79007efcf16a8e6fe61c69981c5e))
34 | * fix the issue of creating wrong type of PositionInfoProvider ([68b65c0](https://www.github.com/guoyingtao/Inchworm/commit/68b65c00e82ba5b57da8a81e2168e6d2ab0b9db1))
35 | * remove optional or force unwrap from ProcessIndicatorModel properties ([078a51f](https://www.github.com/guoyingtao/Inchworm/commit/078a51f1dfe932389965eb375818870c7d6f797a))
36 |
--------------------------------------------------------------------------------
/Example/InchwormExample/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // InchwormExample
4 | //
5 | // Created by Echo on 10/16/19.
6 | // Copyright © 2019 Echo. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
20 | guard let _ = (scene as? UIWindowScene) else { return }
21 | }
22 |
23 | func sceneDidDisconnect(_ scene: UIScene) {
24 | // Called as the scene is being released by the system.
25 | // This occurs shortly after the scene enters the background, or when its session is discarded.
26 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
28 | }
29 |
30 | func sceneDidBecomeActive(_ scene: UIScene) {
31 | // Called when the scene has moved from an inactive state to an active state.
32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
33 | }
34 |
35 | func sceneWillResignActive(_ scene: UIScene) {
36 | // Called when the scene will move from an active state to an inactive state.
37 | // This may occur due to temporary interruptions (ex. an incoming phone call).
38 | }
39 |
40 | func sceneWillEnterForeground(_ scene: UIScene) {
41 | // Called as the scene transitions from the background to the foreground.
42 | // Use this method to undo the changes made on entering the background.
43 | }
44 |
45 | func sceneDidEnterBackground(_ scene: UIScene) {
46 | // Called as the scene transitions from the foreground to the background.
47 | // Use this method to save data, release shared resources, and store enough scene-specific state information
48 | // to restore the scene back to its current state.
49 | }
50 |
51 |
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/Example/InchwormExample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Icon-App-20x20@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "Icon-App-20x20@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "Icon-App-29x29@1x.png",
17 | "idiom" : "iphone",
18 | "scale" : "1x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "Icon-App-29x29@2x.png",
23 | "idiom" : "iphone",
24 | "scale" : "2x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "Icon-App-29x29@3x.png",
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "filename" : "Icon-App-40x40@2x.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "Icon-App-40x40@3x.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "40x40"
44 | },
45 | {
46 | "filename" : "Icon-App-60x60@2x.png",
47 | "idiom" : "iphone",
48 | "scale" : "2x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "Icon-App-60x60@3x.png",
53 | "idiom" : "iphone",
54 | "scale" : "3x",
55 | "size" : "60x60"
56 | },
57 | {
58 | "filename" : "Icon-App-20x20@1x.png",
59 | "idiom" : "ipad",
60 | "scale" : "1x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "Icon-App-20x20@2x.png",
65 | "idiom" : "ipad",
66 | "scale" : "2x",
67 | "size" : "20x20"
68 | },
69 | {
70 | "filename" : "Icon-App-29x29@1x.png",
71 | "idiom" : "ipad",
72 | "scale" : "1x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "Icon-App-29x29@2x.png",
77 | "idiom" : "ipad",
78 | "scale" : "2x",
79 | "size" : "29x29"
80 | },
81 | {
82 | "filename" : "Icon-App-40x40@1x.png",
83 | "idiom" : "ipad",
84 | "scale" : "1x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "Icon-App-40x40@2x.png",
89 | "idiom" : "ipad",
90 | "scale" : "2x",
91 | "size" : "40x40"
92 | },
93 | {
94 | "filename" : "Icon-App-76x76@1x.png",
95 | "idiom" : "ipad",
96 | "scale" : "1x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "Icon-App-76x76@2x.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "76x76"
104 | },
105 | {
106 | "filename" : "Icon-App-83.5x83.5@2x.png",
107 | "idiom" : "ipad",
108 | "scale" : "2x",
109 | "size" : "83.5x83.5"
110 | },
111 | {
112 | "filename" : "ItunesArtwork@2x.png",
113 | "idiom" : "ios-marketing",
114 | "scale" : "1x",
115 | "size" : "1024x1024"
116 | }
117 | ],
118 | "info" : {
119 | "author" : "xcode",
120 | "version" : 1
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Inchworm/Source/SlideRulerPositionHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SlideRulerPositionHelper.swift
3 | // Inchworm
4 | //
5 | // Created by Yingtao Guo on 6/14/23.
6 | //
7 |
8 | import UIKit
9 |
10 | protocol SlideRulerPositionHelper {
11 | var slideRuler: SlideRuler! { get set }
12 | func getInitialOffsetRatio() -> CGFloat
13 | func getCentralDotOriginX() -> CGFloat
14 | func getRulerOffsetX(with progress: CGFloat) -> CGFloat
15 | func checkIsCenterPosition(with limit: CGFloat) -> Bool
16 | func getForceAlignCenterX() -> CGFloat
17 | func getOffsetRatio() -> CGFloat
18 | func handleOffsetRatioWhenScrolling(_ scrollView: UIScrollView)
19 | }
20 |
21 | extension SlideRulerPositionHelper {
22 | func handleOffsetRatioWhenScrolling(_ scrollView: UIScrollView) {}
23 | }
24 |
25 | class UnilateralTypeSlideRulerPositionHelper: SlideRulerPositionHelper {
26 | var slideRuler: SlideRuler!
27 |
28 | func getInitialOffsetRatio() -> CGFloat {
29 | return 0
30 | }
31 |
32 | func getCentralDotOriginX() -> CGFloat {
33 | return slideRuler.pointer.frame.origin.x - slideRuler.dotWidth / 2
34 | }
35 |
36 | func getRulerOffsetX(with progress: CGFloat) -> CGFloat {
37 | return progress * slideRuler.scrollRulerView.frame.width
38 | }
39 |
40 | func checkIsCenterPosition(with limit: CGFloat) -> Bool {
41 | return abs(slideRuler.scrollRulerView.contentOffset.x) < limit
42 | }
43 |
44 | func getForceAlignCenterX() -> CGFloat {
45 | return 0
46 | }
47 |
48 | func getOffsetRatio() -> CGFloat {
49 | let offsetRatio = slideRuler.scrollRulerView.contentOffset.x / slideRuler.scrollRulerView.bounds.width
50 | return min(1, max(0, offsetRatio))
51 | }
52 | }
53 |
54 | class BilateralTypeSlideRulerPositionHelper: SlideRulerPositionHelper {
55 | var slideRuler: SlideRuler!
56 |
57 | func getInitialOffsetRatio() -> CGFloat {
58 | return 0.5
59 | }
60 |
61 | func getCentralDotOriginX() -> CGFloat {
62 | return slideRuler.frame.width - slideRuler.dotWidth / 2
63 | }
64 |
65 | func getRulerOffsetX(with progress: CGFloat) -> CGFloat {
66 | return progress * slideRuler.offsetValue + slideRuler.offsetValue
67 | }
68 |
69 | func checkIsCenterPosition(with limit: CGFloat) -> Bool {
70 | return abs(slideRuler.scrollRulerView.contentOffset.x - slideRuler.frame.width / 2) < limit
71 | }
72 |
73 | func getForceAlignCenterX() -> CGFloat {
74 | return slideRuler.frame.width / 2
75 | }
76 |
77 | func getOffsetRatio() -> CGFloat {
78 | guard slideRuler.offsetValue != 0 else {
79 | return 0
80 | }
81 |
82 | var offsetRatio = (slideRuler.scrollRulerView.contentOffset.x - slideRuler.offsetValue) / slideRuler.offsetValue
83 |
84 | if offsetRatio > 1 { offsetRatio = 1.0 }
85 | if offsetRatio < -1 { offsetRatio = -1.0 }
86 |
87 | return offsetRatio
88 | }
89 |
90 | func handleOffsetRatioWhenScrolling(_ scrollView: UIScrollView) {
91 | if scrollView.frame.width > 0 {
92 | slideRuler.sliderOffsetRatio = scrollView.contentOffset.x / scrollView.frame.width
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Example/InchwormExample/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // InchwormExample
4 | //
5 | // Created by Echo on 10/16/19.
6 | // Copyright © 2019 Echo. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Inchworm
11 |
12 | class ViewController: UIViewController {
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 |
16 | view.backgroundColor = .black
17 |
18 | createHorizontalSlider()
19 | createVerticalSlider()
20 | }
21 |
22 | func createHorizontalSlider() {
23 | let config = Config()
24 | let slider = createSlider(with: config)
25 | slider.translatesAutoresizingMaskIntoConstraints = false
26 |
27 | NSLayoutConstraint.activate([
28 | slider.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -100),
29 | slider.heightAnchor.constraint(equalToConstant: 120),
30 | slider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40),
31 | slider.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
32 | ])
33 | }
34 |
35 | func createVerticalSlider() {
36 | var config = Config()
37 | config.orientation = .vertical
38 | let slider = createSlider(with: config)
39 |
40 | slider.translatesAutoresizingMaskIntoConstraints = false
41 |
42 | NSLayoutConstraint.activate([
43 | slider.widthAnchor.constraint(equalToConstant: 120),
44 | slider.heightAnchor.constraint(equalToConstant: 400),
45 | slider.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
46 | slider.trailingAnchor.constraint(equalTo: view.trailingAnchor)
47 | ])
48 | }
49 |
50 | func createSlider(with config: Config) -> UIView {
51 | let model1 = ProcessIndicatorModel(sliderValueRangeType: .unilateral(limit: 30, defaultValue: 10),
52 | normalIconImage: UIImage(named: "ic_flash_on")!.tinted(with: UIColor.white)!.cgImage!,
53 | dimmedIconImage: UIImage(named: "ic_flash_on")!.tinted(with: UIColor.gray)!.cgImage!)
54 |
55 | let model2 = ProcessIndicatorModel(sliderValueRangeType: .bilateral(limit: 40, defaultValue: 15),
56 | normalIconImage: UIImage(named: "settings")!.tinted(with: UIColor.white)!.cgImage!,
57 | dimmedIconImage: UIImage(named: "settings")!.tinted(with: UIColor.gray)!.cgImage!)
58 |
59 | let model3 = ProcessIndicatorModel(sliderValueRangeType: .bilateral(limit: 50, defaultValue: 0),
60 | normalIconImage: UIImage(named: "ic_camera_front")!.tinted(with: UIColor.white)!.cgImage!,
61 | dimmedIconImage: UIImage(named: "ic_camera_front")!.tinted(with: UIColor.gray)!.cgImage!)
62 |
63 |
64 | let modelList = [model1, model2, model3]
65 |
66 | let slider = Inchworm.createSlider(config: config, frame: .zero, processIndicatorModels: modelList, activeIndex: 1)
67 | slider.delegate = self
68 |
69 | view.addSubview(slider)
70 |
71 | return slider
72 | }
73 | }
74 |
75 | extension ViewController: Inchworm.SliderDelegate {
76 | func didGetOffsetRatio(_ slider: Inchworm.Slider, activeIndicatorIndex: Int, offsetRatio: Float) {
77 | print("No \(activeIndicatorIndex) indicator has a offset(\(offsetRatio))")
78 | }
79 | }
80 |
81 | extension UIImage {
82 | func tinted(with color: UIColor) -> UIImage? {
83 | return UIGraphicsImageRenderer(size: size, format: imageRendererFormat).image { _ in
84 | color.set()
85 | withRenderingMode(.alwaysTemplate).draw(at: .zero)
86 | }
87 | }
88 | }
89 |
90 |
--------------------------------------------------------------------------------
/Inchworm/Source/ProcessIndicatorContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProcessIndicatorContainer.swift
3 | // Inchworm
4 | //
5 | // Created by Echo on 10/16/19.
6 | // Copyright © 2019 Echo. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ProcessIndicatorContainer: UIView {
12 | var processIndicatorViewList: [ProcessIndicatorView] = []
13 | var backgroundSlideView = UIScrollView()
14 | var activeIndicatorIndex = 0
15 | let span: CGFloat = 20
16 | var pageWidth: CGFloat = 0
17 | var iconLength: CGFloat = 0
18 |
19 | var didActive: (Float) -> Void = { _ in }
20 | var didTempReset = {}
21 | var didRemoveTempReset: (Float) -> Void = { _ in }
22 |
23 | var orientation: SliderOrientation = .horizontal
24 |
25 | override var bounds: CGRect {
26 | didSet {
27 | handleBoundsChange()
28 | }
29 | }
30 |
31 | init(orientation: SliderOrientation = .horizontal, frame: CGRect) {
32 | super.init(frame: frame)
33 | self.orientation = orientation
34 |
35 | backgroundSlideView.showsVerticalScrollIndicator = false
36 | backgroundSlideView.showsHorizontalScrollIndicator = false
37 | backgroundSlideView.delegate = self
38 | addSubview(backgroundSlideView)
39 |
40 | setupUIFrames()
41 | }
42 |
43 | required init?(coder: NSCoder) {
44 | super.init(coder: coder)
45 | }
46 |
47 | func handleBoundsChange() {
48 | setupUIFrames()
49 |
50 | processIndicatorViewList.forEach { [weak self] in
51 | $0.frame = CGRect(x: 0, y: 0, width: iconLength, height: iconLength)
52 | self?.setOrientations(for: $0)
53 | }
54 |
55 | reRangeIndicators()
56 | setActiveIndicatorIndex(activeIndicatorIndex)
57 | }
58 |
59 | func setupUIFrames() {
60 | iconLength = min(frame.width, frame.height)
61 | pageWidth = iconLength + span
62 |
63 | backgroundSlideView.frame = bounds
64 | }
65 |
66 | func reRangeIndicators() {
67 | let slideContentSize = getSlideContentSize()
68 | backgroundSlideView.contentSize = CGSize(width: backgroundSlideView.frame.width + slideContentSize.width - iconLength, height: backgroundSlideView.frame.height)
69 |
70 | let startX = backgroundSlideView.contentSize.width / 2 - slideContentSize.width / 2
71 | for i in 0.. CGSize {
105 | guard processIndicatorViewList.count > 0 else {
106 | return backgroundSlideView.contentSize
107 | }
108 |
109 | let width = processIndicatorViewList.map{ $0.frame.width }.reduce(0, +) + span * CGFloat(processIndicatorViewList.count - 1)
110 | let height = backgroundSlideView.contentSize.height
111 |
112 | return CGSize(width: width, height: height)
113 | }
114 |
115 | func initialIndicatorActive() {
116 | getActiveIndicator()?.initialActiveStatus()
117 | }
118 |
119 | func setActiveIndicatorIndex(_ index: Int = 0, animated: Bool = false) {
120 | guard index < processIndicatorViewList.count else {
121 | return
122 | }
123 |
124 | activeIndicatorIndex = index
125 |
126 | guard let indicator = getActiveIndicator() else {
127 | return
128 | }
129 |
130 | indicator.isActive = true
131 |
132 | let slideContentSize = getSlideContentSize()
133 | let currentPositon = indicator.center
134 | let targetPosition = CGPoint(x: (backgroundSlideView.contentSize.width - slideContentSize.width) / 2 + iconLength / 2, y: 0)
135 |
136 | let offset = CGPoint(x: currentPositon.x - targetPosition.x, y: 0)
137 | backgroundSlideView.setContentOffset(offset, animated: animated)
138 | }
139 |
140 | private func getActiveIndicator() -> ProcessIndicatorView? {
141 | guard 0..) {
155 |
156 | let kMaxIndex = processIndicatorViewList.count - 1
157 |
158 | let targetX = scrollView.contentOffset.x + velocity.x * 60.0
159 | var targetIndex = 0
160 |
161 | if (velocity.x > 0) {
162 | targetIndex = Int(ceil(targetX / pageWidth))
163 | } else if (velocity.x == 0) {
164 | targetIndex = Int(round(targetX / pageWidth))
165 | } else if (velocity.x < 0) {
166 | targetIndex = Int(floor(targetX / pageWidth))
167 | }
168 |
169 | if (targetIndex < 0) {
170 | targetIndex = 0
171 | }
172 |
173 | if (targetIndex > kMaxIndex) {
174 | targetIndex = kMaxIndex
175 | }
176 |
177 | targetContentOffset.pointee.x = CGFloat(targetIndex) * pageWidth;
178 |
179 | if targetIndex != activeIndicatorIndex {
180 | setActiveIndicatorIndex(targetIndex)
181 | getActiveIndicator()?.handleTap()
182 | deactiveInactiveIndicators()
183 |
184 | guard let processIndicatorView = getActiveIndicator() else { return }
185 | didActive(processIndicatorView.progress)
186 | }
187 | }
188 | }
189 |
190 | extension ProcessIndicatorContainer: ProcessIndicatorViewDelegate {
191 | private func deactiveInactiveIndicators() {
192 | processIndicatorViewList
193 | .filter { $0.index != activeIndicatorIndex }
194 | .forEach { $0.deactive() }
195 | }
196 |
197 | func didActive(_ processIndicatorView: ProcessIndicatorView) {
198 | if activeIndicatorIndex != processIndicatorView.index {
199 | setActiveIndicatorIndex(processIndicatorView.index, animated: true)
200 | }
201 |
202 | deactiveInactiveIndicators()
203 | didActive(processIndicatorView.progress)
204 | }
205 |
206 | func didTempReset(_ processIndicatorView: ProcessIndicatorView) {
207 | didTempReset()
208 | }
209 |
210 | func didRemoveTempReset(_ processIndicatorView: ProcessIndicatorView) {
211 | didRemoveTempReset(processIndicatorView.progress)
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/Inchworm/Source/Slider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Slider.swift
3 | // Inchworm
4 | //
5 | // Created by Echo on 10/16/19.
6 | // Copyright © 2019 Echo. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol SliderDelegate: AnyObject {
12 | func didGetOffsetRatio(_ slider: Slider, activeIndicatorIndex: Int, offsetRatio: Float)
13 | }
14 |
15 | public class Slider: UIView {
16 | public weak var delegate: SliderDelegate?
17 |
18 | var indicatorContainer: ProcessIndicatorContainer!
19 | var slideRuler: SlideRuler!
20 |
21 | /** For easily rotating the slider when oriention is vertical
22 |
23 | - When orientation is horizontal, indicatorContainer is on the top, sliderRuler is on the bottom.
24 | - When orientation is vertical, indicatorContainer is on the left, sliderRuler is on the right.
25 | */
26 | var baseContainer = UIView()
27 |
28 | var config: Config!
29 |
30 | // You can active different constraints for different orientation
31 | var containerHorizontalWidthConstraint: NSLayoutConstraint!
32 | var containerHoritontalHeightConstraint: NSLayoutConstraint!
33 | var containerVerticalWidthConstraint: NSLayoutConstraint!
34 | var containerVerticalHeightConstraint: NSLayoutConstraint!
35 |
36 | init(config: Config = Config(),
37 | frame: CGRect,
38 | processIndicatorModels: [ProcessIndicatorModel],
39 | activeIndex: Int) {
40 | super.init(frame: frame)
41 |
42 | self.config = config
43 |
44 | createIndicatorContainer()
45 |
46 | processIndicatorModels.forEach {
47 | addIndicatorWith(sliderValueRangeType: $0.sliderValueRangeType,
48 | normalIconImage: $0.normalIconImage,
49 | dimmedIconImage: $0.dimmedIconImage)
50 | }
51 |
52 | setActiveIndicatorIndex(activeIndex)
53 | createSlideRuler()
54 |
55 | baseContainer.frame = bounds
56 | addSubview(baseContainer)
57 | baseContainer.addSubview(indicatorContainer)
58 | baseContainer.addSubview(slideRuler)
59 |
60 | initialAutolayoutConstraint()
61 | adjustContainerByOrientation()
62 | }
63 |
64 | required init?(coder: NSCoder) {
65 | super.init(coder: coder)
66 | }
67 |
68 | func createIndicatorContainer() {
69 | indicatorContainer = ProcessIndicatorContainer(orientation: config.orientation, frame: CGRect(x: 0, y: 0, width: baseContainer.frame.width, height: config.indicatorSpan))
70 |
71 | indicatorContainer.didActive = { [weak self] progress in
72 | self?.setSlideRulerBy(progress: progress)
73 | }
74 |
75 | indicatorContainer.didTempReset = { [weak self] in
76 | guard let self = self else { return }
77 | self.slideRuler.handleTempReset()
78 |
79 | let activeIndex = self.indicatorContainer.activeIndicatorIndex
80 | self.delegate?.didGetOffsetRatio(self, activeIndicatorIndex: activeIndex, offsetRatio: 0)
81 | }
82 |
83 | indicatorContainer.didRemoveTempReset = { [weak self] progress in
84 | self?.setSlideRulerBy(progress: progress)
85 | }
86 | }
87 |
88 | func createSlideRuler() {
89 | let sliderFrame = CGRect(x: 0, y: baseContainer.frame.height / 2, width: baseContainer.frame.width, height: baseContainer.frame.height - config.slideRulerSpan)
90 | let activeIndex = indicatorContainer.activeIndicatorIndex
91 | let indicator = indicatorContainer.processIndicatorViewList[activeIndex]
92 |
93 | slideRuler = SlideRuler(frame: sliderFrame, sliderValueRangeType: indicator.sliderValueRangeType)
94 | slideRuler.delegate = self
95 | slideRuler.forceAlignCenterFeedback = config.forceAlignCenterFeedback
96 | }
97 |
98 | func initialAutolayoutConstraint() {
99 | baseContainer.translatesAutoresizingMaskIntoConstraints = false
100 | indicatorContainer.translatesAutoresizingMaskIntoConstraints = false
101 | slideRuler.translatesAutoresizingMaskIntoConstraints = false
102 |
103 | containerHorizontalWidthConstraint = baseContainer.widthAnchor.constraint(equalTo: widthAnchor)
104 | containerHoritontalHeightConstraint = baseContainer.heightAnchor.constraint(equalTo: heightAnchor)
105 |
106 | containerVerticalHeightConstraint = baseContainer.widthAnchor.constraint(equalTo: heightAnchor)
107 | containerVerticalWidthConstraint = baseContainer.heightAnchor.constraint(equalTo: widthAnchor)
108 |
109 | NSLayoutConstraint.activate([
110 | baseContainer.centerXAnchor.constraint(equalTo: centerXAnchor),
111 | baseContainer.centerYAnchor.constraint(equalTo: centerYAnchor),
112 |
113 | indicatorContainer.leadingAnchor.constraint(equalTo: baseContainer.leadingAnchor),
114 | indicatorContainer.trailingAnchor.constraint(equalTo: baseContainer.trailingAnchor),
115 | indicatorContainer.topAnchor.constraint(equalTo: baseContainer.topAnchor),
116 | indicatorContainer.heightAnchor.constraint(equalToConstant: config.indicatorSpan),
117 |
118 | slideRuler.leadingAnchor.constraint(equalTo: baseContainer.leadingAnchor),
119 | slideRuler.trailingAnchor.constraint(equalTo: baseContainer.trailingAnchor),
120 | slideRuler.topAnchor.constraint(equalTo: indicatorContainer.bottomAnchor, constant: config.spaceBetweenIndicatorAndSlideRule),
121 | slideRuler.heightAnchor.constraint(equalToConstant: config.slideRulerSpan)
122 | ])
123 | }
124 |
125 | // Deactivate first, then activate!
126 | func adjustContainerByOrientation() {
127 | if config.orientation == .horizontal {
128 | NSLayoutConstraint.deactivate([
129 | containerVerticalWidthConstraint,
130 | containerVerticalHeightConstraint
131 | ])
132 |
133 | NSLayoutConstraint.activate([
134 | containerHorizontalWidthConstraint,
135 | containerHoritontalHeightConstraint
136 | ])
137 |
138 | baseContainer.transform = .identity
139 | } else {
140 | NSLayoutConstraint.deactivate([
141 | containerHorizontalWidthConstraint,
142 | containerHoritontalHeightConstraint
143 | ])
144 |
145 | NSLayoutConstraint.activate([
146 | containerVerticalWidthConstraint,
147 | containerVerticalHeightConstraint
148 | ])
149 |
150 | baseContainer.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
151 | }
152 | }
153 |
154 | func addIndicatorWith(sliderValueRangeType: SliderValueRangeType,
155 | normalIconImage: CGImage?,
156 | dimmedIconImage: CGImage?) {
157 | indicatorContainer.addIndicatorWith(sliderValueRangeType: sliderValueRangeType,
158 | normalIconImage: normalIconImage,
159 | dimmedIconImage: dimmedIconImage)
160 | }
161 |
162 | func setActiveIndicatorIndex(_ index: Int = 0) {
163 | indicatorContainer.setActiveIndicatorIndex(index)
164 | }
165 |
166 | func setSlideRulerBy(progress: Float) {
167 | let activeIndex = indicatorContainer.activeIndicatorIndex
168 | let indicator = indicatorContainer.processIndicatorViewList[activeIndex]
169 | slideRuler.setPositionProvider(by: indicator.sliderValueRangeType)
170 | slideRuler.setUIFrames()
171 | slideRuler.handleRemoveTempResetWith(progress: progress)
172 |
173 | delegate?.didGetOffsetRatio(self, activeIndicatorIndex: activeIndex, offsetRatio: progress)
174 | }
175 | }
176 |
177 | extension Slider: SlideRulerDelegate {
178 | func didFinishLayout() {
179 | indicatorContainer.initialIndicatorActive()
180 | }
181 |
182 | func didGetOffsetRatio(from slideRuler: SlideRuler, offsetRatio: CGFloat) {
183 | indicatorContainer.setProgress(Float(offsetRatio))
184 |
185 | let activeIndex = indicatorContainer.activeIndicatorIndex
186 | delegate?.didGetOffsetRatio(self, activeIndicatorIndex: activeIndex, offsetRatio: Float(offsetRatio))
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/Inchworm/Source/SlideRuler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SlideRuler.swift
3 | // Inchworm
4 | //
5 | // Created by Echo on 10/16/19.
6 | // Copyright © 2019 Echo. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private let scaleBarNumber = 41
12 | private let majorScaleBarNumber = 5
13 | private let scaleWidth: CGFloat = 1
14 | private let pointerWidth: CGFloat = 1
15 |
16 | protocol SlideRulerDelegate: AnyObject {
17 | func didFinishLayout()
18 | func didGetOffsetRatio(from slideRuler: SlideRuler, offsetRatio: CGFloat)
19 | }
20 |
21 | class SlideRuler: UIView {
22 | var forceAlignCenterFeedback = true
23 | let pointer = CALayer()
24 | let centralDot = CAShapeLayer()
25 | let scrollRulerView = UIScrollView()
26 | let dotWidth: CGFloat = 6
27 | var sliderOffsetRatio: CGFloat = 0.5
28 | var positionHelper: SlideRulerPositionHelper = BilateralTypeSlideRulerPositionHelper()
29 |
30 | let scaleBarLayer: CAReplicatorLayer = {
31 | var r = CAReplicatorLayer()
32 | r.instanceCount = scaleBarNumber
33 | return r
34 | } ()
35 |
36 | let majorScaleBarLayer: CAReplicatorLayer = {
37 | var r = CAReplicatorLayer()
38 | r.instanceCount = majorScaleBarNumber
39 | return r
40 | } ()
41 |
42 | weak var delegate: SlideRulerDelegate?
43 | var reset = false
44 | var offsetValue: CGFloat = 0
45 |
46 | override var bounds: CGRect {
47 | didSet {
48 | setUIFrames()
49 | delegate?.didFinishLayout()
50 | }
51 | }
52 |
53 | init(frame: CGRect, sliderValueRangeType: SliderValueRangeType) {
54 | super.init(frame: frame)
55 | self.setPositionProvider(by: sliderValueRangeType)
56 | setupUI()
57 | }
58 |
59 | required init?(coder: NSCoder) {
60 | fatalError()
61 | }
62 |
63 | func setPositionProvider(by sliderValueRangeType: SliderValueRangeType) {
64 | switch sliderValueRangeType {
65 | case .bilateral:
66 | self.positionHelper = BilateralTypeSlideRulerPositionHelper()
67 | case .unilateral:
68 | self.positionHelper = UnilateralTypeSlideRulerPositionHelper()
69 | }
70 | self.positionHelper.slideRuler = self
71 | }
72 |
73 | private func setupUI() {
74 | setupSlider()
75 | makeRuler()
76 | makeCentralDot()
77 | makePointer()
78 | }
79 |
80 | @objc func setSliderDelegate() {
81 | scrollRulerView.delegate = self
82 | }
83 |
84 | public func setUIFrames() {
85 | sliderOffsetRatio = positionHelper.getInitialOffsetRatio()
86 | scrollRulerView.frame = bounds
87 |
88 | offsetValue = sliderOffsetRatio * scrollRulerView.frame.width
89 | scrollRulerView.delegate = nil
90 | scrollRulerView.contentSize = CGSize(width: frame.width * 2, height: frame.height)
91 | scrollRulerView.contentOffset = CGPoint(x: offsetValue, y: 0)
92 |
93 | perform(#selector(setSliderDelegate), with: nil, afterDelay: 0.1)
94 | // slider.delegate = self
95 |
96 | pointer.frame = CGRect(x: (frame.width / 2 - pointerWidth / 2), y: bounds.origin.y, width: pointerWidth, height: frame.height)
97 |
98 | let centralDotOriginX = positionHelper.getCentralDotOriginX()
99 | centralDot.frame = CGRect(x: centralDotOriginX, y: frame.height * 0.2, width: dotWidth, height: dotWidth)
100 |
101 | centralDot.path = UIBezierPath(ovalIn: centralDot.bounds).cgPath
102 |
103 | scaleBarLayer.frame = CGRect(x: frame.width / 2, y: 0.6 * frame.height, width: frame.width, height: 0.4 * frame.height)
104 | scaleBarLayer.instanceTransform = CATransform3DMakeTranslation((frame.width - scaleWidth) / CGFloat((scaleBarNumber - 1)), 0, 0)
105 |
106 | scaleBarLayer.sublayers?.forEach {
107 | $0.frame = CGRect(x: 0, y: 0, width: 1, height: scaleBarLayer.frame.height)
108 | }
109 |
110 | majorScaleBarLayer.frame = scaleBarLayer.frame
111 | majorScaleBarLayer.instanceTransform = CATransform3DMakeTranslation((frame.width - scaleWidth) / CGFloat((majorScaleBarNumber - 1)), 0, 0)
112 |
113 | majorScaleBarLayer.sublayers?.forEach {
114 | $0.frame = CGRect(x: 0, y: 0, width: 1, height: majorScaleBarLayer.frame.height)
115 | }
116 | }
117 |
118 | private func setupSlider() {
119 | addSubview(scrollRulerView)
120 |
121 | scrollRulerView.showsHorizontalScrollIndicator = false
122 | scrollRulerView.showsVerticalScrollIndicator = false
123 | scrollRulerView.delegate = self
124 | }
125 |
126 | private func makePointer() {
127 | pointer.backgroundColor = UIColor.white.cgColor
128 | layer.addSublayer(pointer)
129 | }
130 |
131 | private func makeCentralDot() {
132 | centralDot.fillColor = UIColor.white.cgColor
133 | scrollRulerView.layer.addSublayer(centralDot)
134 | }
135 |
136 | private func makeRuler() {
137 | scaleBarLayer.sublayers?.forEach { $0.removeFromSuperlayer() }
138 |
139 | let scaleBar = makeBarScaleMark(byColor: UIColor.gray.cgColor)
140 | scaleBarLayer.addSublayer(scaleBar)
141 |
142 | scrollRulerView.layer.addSublayer(scaleBarLayer)
143 |
144 | majorScaleBarLayer.sublayers?.forEach { $0.removeFromSuperlayer() }
145 |
146 | let majorScaleBar = makeBarScaleMark(byColor: UIColor.white.cgColor)
147 | majorScaleBarLayer.addSublayer(majorScaleBar)
148 |
149 | scrollRulerView.layer.addSublayer(majorScaleBarLayer)
150 | }
151 |
152 | private func makeBarScaleMark(byColor color: CGColor) -> CALayer {
153 | let bar = CALayer()
154 | bar.backgroundColor = color
155 |
156 | return bar
157 | }
158 |
159 | func handleTempReset() {
160 | let offset = CGPoint(x: offsetValue, y: 0)
161 | scrollRulerView.delegate = nil
162 | scrollRulerView.setContentOffset(offset, animated: false)
163 | scrollRulerView.delegate = self
164 |
165 | centralDot.isHidden = true
166 | let color = UIColor.gray.cgColor
167 | scaleBarLayer.sublayers?.forEach { $0.backgroundColor = color}
168 | majorScaleBarLayer.sublayers?.forEach { $0.backgroundColor = color}
169 | }
170 |
171 | func handleRemoveTempResetWith(progress: Float) {
172 | centralDot.fillColor = UIColor.white.cgColor
173 |
174 | scaleBarLayer.sublayers?.forEach { $0.backgroundColor = UIColor.gray.cgColor}
175 | majorScaleBarLayer.sublayers?.forEach { $0.backgroundColor = UIColor.white.cgColor}
176 |
177 | scrollRulerView.delegate = nil
178 |
179 | let offsetX = positionHelper.getRulerOffsetX(with: CGFloat(progress))
180 | let offset = CGPoint(x: offsetX, y: 0)
181 | scrollRulerView.setContentOffset(offset, animated: false)
182 | scrollRulerView.delegate = self
183 |
184 | checkCentralDotHiddenStatus()
185 | }
186 |
187 | func checkCentralDotHiddenStatus() {
188 | centralDot.isHidden = (scrollRulerView.contentOffset.x == frame.width / 2)
189 | }
190 | }
191 |
192 | extension SlideRuler: UIScrollViewDelegate {
193 |
194 | func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
195 | checkCentralDotHiddenStatus()
196 | }
197 |
198 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
199 | centralDot.isHidden = false
200 |
201 | let speed = scrollView.panGestureRecognizer.velocity(in: scrollView.superview)
202 |
203 | let limit = frame.width / CGFloat((scaleBarNumber - 1) * 2)
204 |
205 | func checkIsCenterPosition() -> Bool {
206 | return positionHelper.checkIsCenterPosition(with: limit)
207 | }
208 |
209 | if checkIsCenterPosition() && abs(speed.x) < 10.0 {
210 |
211 | if !reset {
212 | reset = true
213 |
214 | if forceAlignCenterFeedback {
215 | let generator = UIImpactFeedbackGenerator(style: .medium)
216 | generator.impactOccurred()
217 | }
218 |
219 | func forceAlignCenter() {
220 | let offset = CGPoint(x: positionHelper.getForceAlignCenterX(), y: 0)
221 | scrollView.setContentOffset(offset, animated: false)
222 | delegate?.didGetOffsetRatio(from: self, offsetRatio: 0)
223 | }
224 |
225 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
226 | forceAlignCenter()
227 | }
228 |
229 | forceAlignCenter()
230 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.11) {
231 | usleep(1000000)
232 | }
233 | }
234 | } else {
235 | reset = false
236 | }
237 |
238 | let offsetRatio = positionHelper.getOffsetRatio()
239 | delegate?.didGetOffsetRatio(from: self, offsetRatio: offsetRatio)
240 |
241 | positionHelper.handleOffsetRatioWhenScrolling(scrollView)
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/Inchworm/Source/ProcessIndicatorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProcessIndicatorView.swift
3 | // Inchworm
4 | //
5 | // Created by Echo on 10/16/19.
6 | // Copyright © 2019 Echo. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol ProcessIndicatorViewDelegate: AnyObject {
12 | func didActive(_ processIndicatorView: ProcessIndicatorView)
13 | func didTempReset(_ processIndicatorView: ProcessIndicatorView)
14 | func didRemoveTempReset(_ processIndicatorView: ProcessIndicatorView)
15 | }
16 |
17 | class ProcessIndicatorView: UIView {
18 | weak var delegate: ProcessIndicatorViewDelegate?
19 | var viewModel: ProcessIndicatorViewModel!
20 |
21 | private var positiveProgressLayer = CAShapeLayer()
22 | private var minusProgressLayer = CAShapeLayer()
23 | private var progressBaseTrackLayer = CAShapeLayer()
24 | private var progressNumberLayer = CATextLayer()
25 | private var indicatorIconLayer = CALayer()
26 |
27 | let positiveTrackColorVlue = UIColor(displayP3Red: 55.0 / 255.0,
28 | green: 45.0 / 255.0,
29 | blue: 9.0 / 255.0,
30 | alpha: 1)
31 |
32 | let minusTrackColorValue = UIColor(displayP3Red: 84.0 / 255.0,
33 | green: 84.0 / 255.0,
34 | blue: 84.0 / 255.0,
35 | alpha: 1)
36 |
37 | var sliderValueRangeType: SliderValueRangeType {
38 | viewModel.sliderValueRangeType
39 | }
40 |
41 | var normalIconImage: CGImage?
42 | var dimmedIconImage: CGImage?
43 | var index = 0
44 | var isActive: Bool {
45 | get {
46 | viewModel.isActive
47 | }
48 |
49 | set {
50 | viewModel.isActive = newValue
51 | }
52 | }
53 |
54 | var progress: Float {
55 | get {
56 | viewModel.progress
57 | }
58 |
59 | set {
60 | viewModel.progress = newValue
61 | }
62 | }
63 |
64 | private var circlePath: UIBezierPath!
65 |
66 | var positiveProgressColor = UIColor.white {
67 | didSet {
68 | positiveProgressLayer.strokeColor = positiveProgressColor.cgColor
69 | }
70 | }
71 |
72 | var minusProgressColor = UIColor.white {
73 | didSet {
74 | minusProgressLayer.strokeColor = minusProgressColor.cgColor
75 | }
76 | }
77 |
78 | var progressBaseTrackColor = UIColor.white {
79 | didSet {
80 | progressBaseTrackLayer.strokeColor = progressBaseTrackColor.cgColor
81 | }
82 | }
83 |
84 | override var frame: CGRect {
85 | didSet {
86 | layoutUIs()
87 | }
88 | }
89 |
90 | init(frame: CGRect,
91 | viewModel: ProcessIndicatorViewModel,
92 | normalIconImage: CGImage? = nil,
93 | dimmedIconImage: CGImage? = nil) {
94 | super.init(frame: frame)
95 |
96 | self.viewModel = viewModel
97 | self.normalIconImage = normalIconImage
98 | self.dimmedIconImage = dimmedIconImage
99 |
100 | createUIs()
101 | setupViewModel()
102 | makeTappable()
103 | }
104 |
105 | required init?(coder aDecoder: NSCoder) {
106 | super.init(coder: aDecoder)
107 | }
108 |
109 | func createUIs() {
110 | createCircularProgressIndicator()
111 | createProgressNumberIndicator()
112 | createIndicatorIcon()
113 | layoutUIs()
114 | }
115 |
116 | func layoutUIs() {
117 | circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width/2, y: frame.size.height/2), radius: (frame.size.width - 1.5)/2, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(1.5 * .pi), clockwise: true)
118 |
119 | let iconLayerLength = frame.width / 2
120 |
121 | indicatorIconLayer.frame = CGRect(x: frame.width / 2 - iconLayerLength / 2 , y: frame.height / 2 - iconLayerLength / 2 , width: iconLayerLength, height: iconLayerLength)
122 |
123 | progressNumberLayer.frame = CGRect(x: layer.bounds.origin.x, y: ((layer.bounds.height - progressNumberLayer.fontSize) / 2), width: layer.bounds.width, height: layer.bounds.height)
124 |
125 | layer.cornerRadius = self.frame.size.width/2
126 | progressBaseTrackLayer.path = circlePath.cgPath
127 | positiveProgressLayer.path = circlePath.cgPath
128 | minusProgressLayer.path = circlePath.reversing().cgPath
129 | }
130 |
131 | func setupViewModel() {
132 | viewModel.didSetProgress = { [weak self] progressValue in
133 | guard let self = self else { return }
134 | self.setProgress(ratio: self.viewModel.progress, value: progressValue)
135 | }
136 |
137 | viewModel.didSetStatus = { [weak self] status in
138 | self?.change(to: status)
139 | }
140 |
141 | viewModel.initialize()
142 | }
143 |
144 | func initialActiveStatus() {
145 | isActive = true
146 |
147 | if progress != 0 {
148 | delegate?.didActive(self)
149 | viewModel.status = .editing
150 | }
151 | }
152 |
153 | func deactive() {
154 | viewModel.deactive()
155 | }
156 |
157 | func makeTappable() {
158 | let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
159 | addGestureRecognizer(tap)
160 | }
161 |
162 | @objc func handleTap() {
163 | if isActive {
164 | if viewModel.status == .editing {
165 | viewModel.status = .reset
166 | delegate?.didTempReset(self)
167 | } else {
168 | viewModel.status = .editing
169 | delegate?.didRemoveTempReset(self)
170 | }
171 | } else {
172 | isActive = true
173 | delegate?.didActive(self)
174 |
175 | if viewModel.status == .reset {
176 | delegate?.didTempReset(self)
177 | } else {
178 | viewModel.status = .editing
179 | }
180 | }
181 | }
182 |
183 | private func createIndicatorIcon() {
184 | indicatorIconLayer.contentsGravity = .resizeAspect
185 | layer.addSublayer(indicatorIconLayer)
186 | }
187 |
188 | private func createCircularProgressIndicator() {
189 | backgroundColor = UIColor.clear
190 | progressBaseTrackLayer.fillColor = UIColor.clear.cgColor
191 | progressBaseTrackLayer.strokeColor = progressBaseTrackColor.cgColor
192 | progressBaseTrackLayer.lineWidth = 2.0
193 | progressBaseTrackLayer.strokeEnd = 1.0
194 | layer.addSublayer(progressBaseTrackLayer)
195 |
196 | positiveProgressLayer = getProgressLayer(by: circlePath.cgPath, and: positiveProgressColor.cgColor)
197 | layer.addSublayer(positiveProgressLayer)
198 |
199 | minusProgressLayer = getProgressLayer(by: circlePath.reversing().cgPath, and: minusProgressColor.cgColor)
200 | layer.addSublayer(minusProgressLayer)
201 | }
202 |
203 | private func getProgressLayer(by path: CGPath, and color: CGColor) -> CAShapeLayer {
204 | let layer = CAShapeLayer()
205 | layer.path = path
206 | layer.fillColor = UIColor.clear.cgColor
207 | layer.strokeColor = color
208 | layer.lineWidth = 2.0
209 | layer.strokeEnd = 0.0
210 |
211 | return layer
212 | }
213 |
214 | private func createProgressNumberIndicator() {
215 | progressNumberLayer = CATextLayer()
216 | progressNumberLayer.foregroundColor = UIColor.white.cgColor
217 | progressNumberLayer.contentsScale = UIScreen.main.scale
218 | progressNumberLayer.string = "0"
219 | progressNumberLayer.fontSize = 16
220 | progressNumberLayer.alignmentMode = .center
221 |
222 | layer.addSublayer(progressNumberLayer)
223 | }
224 |
225 | private func setProgress(ratio progressRatio: Float, value progressValue: Int) {
226 | viewModel.status = .editing
227 |
228 | positiveProgressLayer.strokeEnd = 0
229 |
230 | if progressRatio > 0 {
231 | minusProgressLayer.strokeEnd = 0
232 | positiveProgressLayer.isHidden = false
233 |
234 | positiveProgressLayer.path = circlePath.cgPath
235 | positiveProgressColor = UIColor(displayP3Red: 247.0 / 255.0, green: 198.0 / 255.0, blue: 0, alpha: 1)
236 | progressBaseTrackColor = positiveTrackColorVlue
237 | positiveProgressLayer.strokeColor = positiveProgressColor.cgColor
238 | positiveProgressLayer.strokeEnd = abs(CGFloat(progressRatio))
239 | progressNumberLayer.foregroundColor = positiveProgressColor.cgColor
240 | } else {
241 | positiveProgressLayer.strokeEnd = 0
242 | minusProgressLayer.isHidden = false
243 |
244 | minusProgressColor = .white
245 | progressBaseTrackColor = minusTrackColorValue
246 | minusProgressLayer.strokeColor = minusProgressColor.cgColor
247 | minusProgressLayer.strokeEnd = abs(CGFloat(progressRatio))
248 | progressNumberLayer.foregroundColor = minusProgressColor.cgColor
249 | }
250 |
251 | progressBaseTrackLayer.strokeColor = progressBaseTrackColor.cgColor
252 | progressNumberLayer.string = "\(progressValue)"
253 | }
254 |
255 | private func change(to status: IndicatorStatus) {
256 | indicatorIconLayer.isHidden = false
257 | progressNumberLayer.isHidden = true
258 | progressBaseTrackLayer.strokeColor = progressBaseTrackColor.cgColor
259 | positiveProgressLayer.isHidden = true
260 | minusProgressLayer.isHidden = true
261 |
262 | switch status {
263 | case .initial:
264 | indicatorIconLayer.contents = normalIconImage
265 | progressBaseTrackLayer.strokeColor = UIColor.white.cgColor
266 | case .reset:
267 | indicatorIconLayer.contents = dimmedIconImage
268 | progressBaseTrackLayer.strokeColor = UIColor.gray.cgColor
269 | case .editing:
270 | indicatorIconLayer.contents = normalIconImage
271 | progressBaseTrackLayer.strokeColor = (progress > 0 ? positiveTrackColorVlue : minusTrackColorValue).cgColor
272 | indicatorIconLayer.isHidden = true
273 | progressNumberLayer.isHidden = false
274 | positiveProgressLayer.isHidden = false
275 | minusProgressLayer.isHidden = false
276 | case .deactive:
277 | indicatorIconLayer.contents = normalIconImage
278 | progressBaseTrackLayer.strokeColor = (progress > 0 ? positiveTrackColorVlue : minusTrackColorValue).cgColor
279 | indicatorIconLayer.isHidden = false
280 | progressNumberLayer.isHidden = true
281 | positiveProgressLayer.isHidden = false
282 | minusProgressLayer.isHidden = false
283 | }
284 | }
285 | }
286 |
287 | enum IndicatorStatus {
288 | case initial
289 | case reset
290 | case editing
291 | case deactive
292 | }
293 |
--------------------------------------------------------------------------------
/Example/InchwormExample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 51;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0248093C09A428FEBFE33777 /* Pods_InchwormExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ECDDD23A23B8950787EFB9C /* Pods_InchwormExample.framework */; };
11 | 5F732C1223579D89008B53DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F732C1123579D89008B53DA /* AppDelegate.swift */; };
12 | 5F732C1423579D89008B53DA /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F732C1323579D89008B53DA /* SceneDelegate.swift */; };
13 | 5F732C1623579D89008B53DA /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F732C1523579D89008B53DA /* ViewController.swift */; };
14 | 5F732C1923579D89008B53DA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5F732C1723579D89008B53DA /* Main.storyboard */; };
15 | 5F732C1B23579D8A008B53DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5F732C1A23579D8A008B53DA /* Assets.xcassets */; };
16 | 5F732C1E23579D8A008B53DA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5F732C1C23579D8A008B53DA /* LaunchScreen.storyboard */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXFileReference section */
20 | 387DEA6631C4BA5353671ABB /* Pods-InchwormExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InchwormExample.debug.xcconfig"; path = "Target Support Files/Pods-InchwormExample/Pods-InchwormExample.debug.xcconfig"; sourceTree = ""; };
21 | 5F732C0E23579D89008B53DA /* InchwormExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InchwormExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
22 | 5F732C1123579D89008B53DA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
23 | 5F732C1323579D89008B53DA /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
24 | 5F732C1523579D89008B53DA /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
25 | 5F732C1823579D89008B53DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
26 | 5F732C1A23579D8A008B53DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
27 | 5F732C1D23579D8A008B53DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
28 | 5F732C1F23579D8A008B53DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | 7ECDDD23A23B8950787EFB9C /* Pods_InchwormExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_InchwormExample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
30 | F66A13EA16CA78EC2BD806AE /* Pods-InchwormExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InchwormExample.release.xcconfig"; path = "Target Support Files/Pods-InchwormExample/Pods-InchwormExample.release.xcconfig"; sourceTree = ""; };
31 | /* End PBXFileReference section */
32 |
33 | /* Begin PBXFrameworksBuildPhase section */
34 | 5F732C0B23579D89008B53DA /* Frameworks */ = {
35 | isa = PBXFrameworksBuildPhase;
36 | buildActionMask = 2147483647;
37 | files = (
38 | 0248093C09A428FEBFE33777 /* Pods_InchwormExample.framework in Frameworks */,
39 | );
40 | runOnlyForDeploymentPostprocessing = 0;
41 | };
42 | /* End PBXFrameworksBuildPhase section */
43 |
44 | /* Begin PBXGroup section */
45 | 2B77AA83CBA926E560FEF8A4 /* Frameworks */ = {
46 | isa = PBXGroup;
47 | children = (
48 | 7ECDDD23A23B8950787EFB9C /* Pods_InchwormExample.framework */,
49 | );
50 | name = Frameworks;
51 | sourceTree = "";
52 | };
53 | 5F732C0523579D89008B53DA = {
54 | isa = PBXGroup;
55 | children = (
56 | 5F732C1023579D89008B53DA /* InchwormExample */,
57 | 5F732C0F23579D89008B53DA /* Products */,
58 | ADFCEBF82B550BFFB9EC8CF9 /* Pods */,
59 | 2B77AA83CBA926E560FEF8A4 /* Frameworks */,
60 | );
61 | sourceTree = "";
62 | };
63 | 5F732C0F23579D89008B53DA /* Products */ = {
64 | isa = PBXGroup;
65 | children = (
66 | 5F732C0E23579D89008B53DA /* InchwormExample.app */,
67 | );
68 | name = Products;
69 | sourceTree = "";
70 | };
71 | 5F732C1023579D89008B53DA /* InchwormExample */ = {
72 | isa = PBXGroup;
73 | children = (
74 | 5F732C1123579D89008B53DA /* AppDelegate.swift */,
75 | 5F732C1323579D89008B53DA /* SceneDelegate.swift */,
76 | 5F732C1523579D89008B53DA /* ViewController.swift */,
77 | 5F732C1723579D89008B53DA /* Main.storyboard */,
78 | 5F732C1A23579D8A008B53DA /* Assets.xcassets */,
79 | 5F732C1C23579D8A008B53DA /* LaunchScreen.storyboard */,
80 | 5F732C1F23579D8A008B53DA /* Info.plist */,
81 | );
82 | path = InchwormExample;
83 | sourceTree = "";
84 | };
85 | ADFCEBF82B550BFFB9EC8CF9 /* Pods */ = {
86 | isa = PBXGroup;
87 | children = (
88 | 387DEA6631C4BA5353671ABB /* Pods-InchwormExample.debug.xcconfig */,
89 | F66A13EA16CA78EC2BD806AE /* Pods-InchwormExample.release.xcconfig */,
90 | );
91 | path = Pods;
92 | sourceTree = "";
93 | };
94 | /* End PBXGroup section */
95 |
96 | /* Begin PBXNativeTarget section */
97 | 5F732C0D23579D89008B53DA /* InchwormExample */ = {
98 | isa = PBXNativeTarget;
99 | buildConfigurationList = 5F732C2223579D8A008B53DA /* Build configuration list for PBXNativeTarget "InchwormExample" */;
100 | buildPhases = (
101 | 59EC2A57CB8171EEC8B30943 /* [CP] Check Pods Manifest.lock */,
102 | 5F732C0A23579D89008B53DA /* Sources */,
103 | 5F732C0B23579D89008B53DA /* Frameworks */,
104 | 5F732C0C23579D89008B53DA /* Resources */,
105 | 4556AE16A243FC4080D5F3CF /* [CP] Embed Pods Frameworks */,
106 | );
107 | buildRules = (
108 | );
109 | dependencies = (
110 | );
111 | name = InchwormExample;
112 | productName = InchwormExample;
113 | productReference = 5F732C0E23579D89008B53DA /* InchwormExample.app */;
114 | productType = "com.apple.product-type.application";
115 | };
116 | /* End PBXNativeTarget section */
117 |
118 | /* Begin PBXProject section */
119 | 5F732C0623579D89008B53DA /* Project object */ = {
120 | isa = PBXProject;
121 | attributes = {
122 | LastSwiftUpdateCheck = 1110;
123 | LastUpgradeCheck = 1250;
124 | ORGANIZATIONNAME = Echo;
125 | TargetAttributes = {
126 | 5F732C0D23579D89008B53DA = {
127 | CreatedOnToolsVersion = 11.1;
128 | };
129 | };
130 | };
131 | buildConfigurationList = 5F732C0923579D89008B53DA /* Build configuration list for PBXProject "InchwormExample" */;
132 | compatibilityVersion = "Xcode 9.3";
133 | developmentRegion = en;
134 | hasScannedForEncodings = 0;
135 | knownRegions = (
136 | en,
137 | Base,
138 | );
139 | mainGroup = 5F732C0523579D89008B53DA;
140 | productRefGroup = 5F732C0F23579D89008B53DA /* Products */;
141 | projectDirPath = "";
142 | projectRoot = "";
143 | targets = (
144 | 5F732C0D23579D89008B53DA /* InchwormExample */,
145 | );
146 | };
147 | /* End PBXProject section */
148 |
149 | /* Begin PBXResourcesBuildPhase section */
150 | 5F732C0C23579D89008B53DA /* Resources */ = {
151 | isa = PBXResourcesBuildPhase;
152 | buildActionMask = 2147483647;
153 | files = (
154 | 5F732C1E23579D8A008B53DA /* LaunchScreen.storyboard in Resources */,
155 | 5F732C1B23579D8A008B53DA /* Assets.xcassets in Resources */,
156 | 5F732C1923579D89008B53DA /* Main.storyboard in Resources */,
157 | );
158 | runOnlyForDeploymentPostprocessing = 0;
159 | };
160 | /* End PBXResourcesBuildPhase section */
161 |
162 | /* Begin PBXShellScriptBuildPhase section */
163 | 4556AE16A243FC4080D5F3CF /* [CP] Embed Pods Frameworks */ = {
164 | isa = PBXShellScriptBuildPhase;
165 | buildActionMask = 2147483647;
166 | files = (
167 | );
168 | inputFileListPaths = (
169 | "${PODS_ROOT}/Target Support Files/Pods-InchwormExample/Pods-InchwormExample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
170 | );
171 | name = "[CP] Embed Pods Frameworks";
172 | outputFileListPaths = (
173 | "${PODS_ROOT}/Target Support Files/Pods-InchwormExample/Pods-InchwormExample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
174 | );
175 | runOnlyForDeploymentPostprocessing = 0;
176 | shellPath = /bin/sh;
177 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-InchwormExample/Pods-InchwormExample-frameworks.sh\"\n";
178 | showEnvVarsInLog = 0;
179 | };
180 | 59EC2A57CB8171EEC8B30943 /* [CP] Check Pods Manifest.lock */ = {
181 | isa = PBXShellScriptBuildPhase;
182 | buildActionMask = 2147483647;
183 | files = (
184 | );
185 | inputFileListPaths = (
186 | );
187 | inputPaths = (
188 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
189 | "${PODS_ROOT}/Manifest.lock",
190 | );
191 | name = "[CP] Check Pods Manifest.lock";
192 | outputFileListPaths = (
193 | );
194 | outputPaths = (
195 | "$(DERIVED_FILE_DIR)/Pods-InchwormExample-checkManifestLockResult.txt",
196 | );
197 | runOnlyForDeploymentPostprocessing = 0;
198 | shellPath = /bin/sh;
199 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
200 | showEnvVarsInLog = 0;
201 | };
202 | /* End PBXShellScriptBuildPhase section */
203 |
204 | /* Begin PBXSourcesBuildPhase section */
205 | 5F732C0A23579D89008B53DA /* Sources */ = {
206 | isa = PBXSourcesBuildPhase;
207 | buildActionMask = 2147483647;
208 | files = (
209 | 5F732C1623579D89008B53DA /* ViewController.swift in Sources */,
210 | 5F732C1223579D89008B53DA /* AppDelegate.swift in Sources */,
211 | 5F732C1423579D89008B53DA /* SceneDelegate.swift in Sources */,
212 | );
213 | runOnlyForDeploymentPostprocessing = 0;
214 | };
215 | /* End PBXSourcesBuildPhase section */
216 |
217 | /* Begin PBXVariantGroup section */
218 | 5F732C1723579D89008B53DA /* Main.storyboard */ = {
219 | isa = PBXVariantGroup;
220 | children = (
221 | 5F732C1823579D89008B53DA /* Base */,
222 | );
223 | name = Main.storyboard;
224 | sourceTree = "";
225 | };
226 | 5F732C1C23579D8A008B53DA /* LaunchScreen.storyboard */ = {
227 | isa = PBXVariantGroup;
228 | children = (
229 | 5F732C1D23579D8A008B53DA /* Base */,
230 | );
231 | name = LaunchScreen.storyboard;
232 | sourceTree = "";
233 | };
234 | /* End PBXVariantGroup section */
235 |
236 | /* Begin XCBuildConfiguration section */
237 | 5F732C2023579D8A008B53DA /* Debug */ = {
238 | isa = XCBuildConfiguration;
239 | buildSettings = {
240 | ALWAYS_SEARCH_USER_PATHS = NO;
241 | CLANG_ANALYZER_NONNULL = YES;
242 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
244 | CLANG_CXX_LIBRARY = "libc++";
245 | CLANG_ENABLE_MODULES = YES;
246 | CLANG_ENABLE_OBJC_ARC = YES;
247 | CLANG_ENABLE_OBJC_WEAK = YES;
248 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
249 | CLANG_WARN_BOOL_CONVERSION = YES;
250 | CLANG_WARN_COMMA = YES;
251 | CLANG_WARN_CONSTANT_CONVERSION = YES;
252 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
253 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
254 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
255 | CLANG_WARN_EMPTY_BODY = YES;
256 | CLANG_WARN_ENUM_CONVERSION = YES;
257 | CLANG_WARN_INFINITE_RECURSION = YES;
258 | CLANG_WARN_INT_CONVERSION = YES;
259 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
260 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
261 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
262 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
263 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
264 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
265 | CLANG_WARN_STRICT_PROTOTYPES = YES;
266 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
267 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
268 | CLANG_WARN_UNREACHABLE_CODE = YES;
269 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
270 | COPY_PHASE_STRIP = NO;
271 | DEBUG_INFORMATION_FORMAT = dwarf;
272 | ENABLE_STRICT_OBJC_MSGSEND = YES;
273 | ENABLE_TESTABILITY = YES;
274 | GCC_C_LANGUAGE_STANDARD = gnu11;
275 | GCC_DYNAMIC_NO_PIC = NO;
276 | GCC_NO_COMMON_BLOCKS = YES;
277 | GCC_OPTIMIZATION_LEVEL = 0;
278 | GCC_PREPROCESSOR_DEFINITIONS = (
279 | "DEBUG=1",
280 | "$(inherited)",
281 | );
282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
284 | GCC_WARN_UNDECLARED_SELECTOR = YES;
285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
286 | GCC_WARN_UNUSED_FUNCTION = YES;
287 | GCC_WARN_UNUSED_VARIABLE = YES;
288 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
289 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
290 | MTL_FAST_MATH = YES;
291 | ONLY_ACTIVE_ARCH = YES;
292 | SDKROOT = iphoneos;
293 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
294 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
295 | };
296 | name = Debug;
297 | };
298 | 5F732C2123579D8A008B53DA /* Release */ = {
299 | isa = XCBuildConfiguration;
300 | buildSettings = {
301 | ALWAYS_SEARCH_USER_PATHS = NO;
302 | CLANG_ANALYZER_NONNULL = YES;
303 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
304 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
305 | CLANG_CXX_LIBRARY = "libc++";
306 | CLANG_ENABLE_MODULES = YES;
307 | CLANG_ENABLE_OBJC_ARC = YES;
308 | CLANG_ENABLE_OBJC_WEAK = YES;
309 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
310 | CLANG_WARN_BOOL_CONVERSION = YES;
311 | CLANG_WARN_COMMA = YES;
312 | CLANG_WARN_CONSTANT_CONVERSION = YES;
313 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
315 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
316 | CLANG_WARN_EMPTY_BODY = YES;
317 | CLANG_WARN_ENUM_CONVERSION = YES;
318 | CLANG_WARN_INFINITE_RECURSION = YES;
319 | CLANG_WARN_INT_CONVERSION = YES;
320 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
321 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
322 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
323 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
324 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
326 | CLANG_WARN_STRICT_PROTOTYPES = YES;
327 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
328 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
329 | CLANG_WARN_UNREACHABLE_CODE = YES;
330 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
331 | COPY_PHASE_STRIP = NO;
332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
333 | ENABLE_NS_ASSERTIONS = NO;
334 | ENABLE_STRICT_OBJC_MSGSEND = YES;
335 | GCC_C_LANGUAGE_STANDARD = gnu11;
336 | GCC_NO_COMMON_BLOCKS = YES;
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 = 13.0;
344 | MTL_ENABLE_DEBUG_INFO = NO;
345 | MTL_FAST_MATH = YES;
346 | SDKROOT = iphoneos;
347 | SWIFT_COMPILATION_MODE = wholemodule;
348 | SWIFT_OPTIMIZATION_LEVEL = "-O";
349 | VALIDATE_PRODUCT = YES;
350 | };
351 | name = Release;
352 | };
353 | 5F732C2323579D8A008B53DA /* Debug */ = {
354 | isa = XCBuildConfiguration;
355 | baseConfigurationReference = 387DEA6631C4BA5353671ABB /* Pods-InchwormExample.debug.xcconfig */;
356 | buildSettings = {
357 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
358 | CODE_SIGN_STYLE = Automatic;
359 | DEVELOPMENT_TEAM = 3V26Q55U3S;
360 | INFOPLIST_FILE = InchwormExample/Info.plist;
361 | LD_RUNPATH_SEARCH_PATHS = (
362 | "$(inherited)",
363 | "@executable_path/Frameworks",
364 | );
365 | MARKETING_VERSION = 0.1;
366 | PRODUCT_BUNDLE_IDENTIFIER = com.echo.InchwormExample;
367 | PRODUCT_NAME = "$(TARGET_NAME)";
368 | SWIFT_VERSION = 5.0;
369 | TARGETED_DEVICE_FAMILY = "1,2";
370 | };
371 | name = Debug;
372 | };
373 | 5F732C2423579D8A008B53DA /* Release */ = {
374 | isa = XCBuildConfiguration;
375 | baseConfigurationReference = F66A13EA16CA78EC2BD806AE /* Pods-InchwormExample.release.xcconfig */;
376 | buildSettings = {
377 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
378 | CODE_SIGN_STYLE = Automatic;
379 | DEVELOPMENT_TEAM = 3V26Q55U3S;
380 | INFOPLIST_FILE = InchwormExample/Info.plist;
381 | LD_RUNPATH_SEARCH_PATHS = (
382 | "$(inherited)",
383 | "@executable_path/Frameworks",
384 | );
385 | MARKETING_VERSION = 0.1;
386 | PRODUCT_BUNDLE_IDENTIFIER = com.echo.InchwormExample;
387 | PRODUCT_NAME = "$(TARGET_NAME)";
388 | SWIFT_VERSION = 5.0;
389 | TARGETED_DEVICE_FAMILY = "1,2";
390 | };
391 | name = Release;
392 | };
393 | /* End XCBuildConfiguration section */
394 |
395 | /* Begin XCConfigurationList section */
396 | 5F732C0923579D89008B53DA /* Build configuration list for PBXProject "InchwormExample" */ = {
397 | isa = XCConfigurationList;
398 | buildConfigurations = (
399 | 5F732C2023579D8A008B53DA /* Debug */,
400 | 5F732C2123579D8A008B53DA /* Release */,
401 | );
402 | defaultConfigurationIsVisible = 0;
403 | defaultConfigurationName = Release;
404 | };
405 | 5F732C2223579D8A008B53DA /* Build configuration list for PBXNativeTarget "InchwormExample" */ = {
406 | isa = XCConfigurationList;
407 | buildConfigurations = (
408 | 5F732C2323579D8A008B53DA /* Debug */,
409 | 5F732C2423579D8A008B53DA /* Release */,
410 | );
411 | defaultConfigurationIsVisible = 0;
412 | defaultConfigurationName = Release;
413 | };
414 | /* End XCConfigurationList section */
415 | };
416 | rootObject = 5F732C0623579D89008B53DA /* Project object */;
417 | }
418 |
--------------------------------------------------------------------------------
/Inchworm.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5F732BED23578DF2008B53DA /* Inchworm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F732BE323578DF2008B53DA /* Inchworm.framework */; };
11 | 5F732BF223578DF2008B53DA /* InchwormTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F732BF123578DF2008B53DA /* InchwormTests.swift */; };
12 | 5F732BF423578DF2008B53DA /* Inchworm.h in Headers */ = {isa = PBXBuildFile; fileRef = 5F732BE623578DF2008B53DA /* Inchworm.h */; settings = {ATTRIBUTES = (Public, ); }; };
13 | 5F732BFE23578E57008B53DA /* Inchworm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F732BFD23578E57008B53DA /* Inchworm.swift */; };
14 | 5F732C0223578ED7008B53DA /* ProcessIndicatorContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F732C0123578ED7008B53DA /* ProcessIndicatorContainer.swift */; };
15 | 5F732C0423578F67008B53DA /* SlideRuler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F732C0323578F67008B53DA /* SlideRuler.swift */; };
16 | 5F732C30235942BD008B53DA /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 5F732C2F235942BD008B53DA /* README.md */; };
17 | 5FE0BBA2235D4CDB007EFB8E /* Inchworm.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 5FE0BBA1235D4CDB007EFB8E /* Inchworm.podspec */; };
18 | 5FE0BBA5235DFB10007EFB8E /* Slider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE0BBA3235DFB10007EFB8E /* Slider.swift */; };
19 | 5FE0BBA6235DFB10007EFB8E /* ProcessIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE0BBA4235DFB10007EFB8E /* ProcessIndicatorView.swift */; };
20 | F2058F2F2A42A383008DEBAA /* ProcessIndicatorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2058F2E2A42A383008DEBAA /* ProcessIndicatorViewModel.swift */; };
21 | F2B4FDAA2A3CA65900667F22 /* SlideRulerPositionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2B4FDA92A3CA65900667F22 /* SlideRulerPositionHelper.swift */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXContainerItemProxy section */
25 | 5F732BEE23578DF2008B53DA /* PBXContainerItemProxy */ = {
26 | isa = PBXContainerItemProxy;
27 | containerPortal = 5F732BDA23578DF2008B53DA /* Project object */;
28 | proxyType = 1;
29 | remoteGlobalIDString = 5F732BE223578DF2008B53DA;
30 | remoteInfo = Inchworm;
31 | };
32 | /* End PBXContainerItemProxy section */
33 |
34 | /* Begin PBXFileReference section */
35 | 5F732BE323578DF2008B53DA /* Inchworm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Inchworm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
36 | 5F732BE623578DF2008B53DA /* Inchworm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Inchworm.h; sourceTree = ""; };
37 | 5F732BE723578DF2008B53DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
38 | 5F732BEC23578DF2008B53DA /* InchwormTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InchwormTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
39 | 5F732BF123578DF2008B53DA /* InchwormTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InchwormTests.swift; sourceTree = ""; };
40 | 5F732BF323578DF2008B53DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
41 | 5F732BFD23578E57008B53DA /* Inchworm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inchworm.swift; sourceTree = ""; };
42 | 5F732C0123578ED7008B53DA /* ProcessIndicatorContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessIndicatorContainer.swift; sourceTree = ""; };
43 | 5F732C0323578F67008B53DA /* SlideRuler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideRuler.swift; sourceTree = ""; };
44 | 5F732C2F235942BD008B53DA /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
45 | 5FE0BBA1235D4CDB007EFB8E /* Inchworm.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = Inchworm.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
46 | 5FE0BBA3235DFB10007EFB8E /* Slider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Slider.swift; sourceTree = ""; };
47 | 5FE0BBA4235DFB10007EFB8E /* ProcessIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessIndicatorView.swift; sourceTree = ""; };
48 | F2058F2E2A42A383008DEBAA /* ProcessIndicatorViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessIndicatorViewModel.swift; sourceTree = ""; };
49 | F2B4FDA92A3CA65900667F22 /* SlideRulerPositionHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SlideRulerPositionHelper.swift; sourceTree = ""; };
50 | /* End PBXFileReference section */
51 |
52 | /* Begin PBXFrameworksBuildPhase section */
53 | 5F732BE023578DF2008B53DA /* Frameworks */ = {
54 | isa = PBXFrameworksBuildPhase;
55 | buildActionMask = 2147483647;
56 | files = (
57 | );
58 | runOnlyForDeploymentPostprocessing = 0;
59 | };
60 | 5F732BE923578DF2008B53DA /* Frameworks */ = {
61 | isa = PBXFrameworksBuildPhase;
62 | buildActionMask = 2147483647;
63 | files = (
64 | 5F732BED23578DF2008B53DA /* Inchworm.framework in Frameworks */,
65 | );
66 | runOnlyForDeploymentPostprocessing = 0;
67 | };
68 | /* End PBXFrameworksBuildPhase section */
69 |
70 | /* Begin PBXGroup section */
71 | 5F732BD923578DF2008B53DA = {
72 | isa = PBXGroup;
73 | children = (
74 | 5FE0BBA1235D4CDB007EFB8E /* Inchworm.podspec */,
75 | 5F732C2F235942BD008B53DA /* README.md */,
76 | 5F732BE523578DF2008B53DA /* Inchworm */,
77 | 5F732BF023578DF2008B53DA /* InchwormTests */,
78 | 5F732BE423578DF2008B53DA /* Products */,
79 | );
80 | sourceTree = "";
81 | };
82 | 5F732BE423578DF2008B53DA /* Products */ = {
83 | isa = PBXGroup;
84 | children = (
85 | 5F732BE323578DF2008B53DA /* Inchworm.framework */,
86 | 5F732BEC23578DF2008B53DA /* InchwormTests.xctest */,
87 | );
88 | name = Products;
89 | sourceTree = "";
90 | };
91 | 5F732BE523578DF2008B53DA /* Inchworm */ = {
92 | isa = PBXGroup;
93 | children = (
94 | 5FFD2417235E37FA00602F3A /* Source */,
95 | 5F732BE623578DF2008B53DA /* Inchworm.h */,
96 | 5F732BE723578DF2008B53DA /* Info.plist */,
97 | );
98 | path = Inchworm;
99 | sourceTree = "";
100 | };
101 | 5F732BF023578DF2008B53DA /* InchwormTests */ = {
102 | isa = PBXGroup;
103 | children = (
104 | 5F732BF123578DF2008B53DA /* InchwormTests.swift */,
105 | 5F732BF323578DF2008B53DA /* Info.plist */,
106 | );
107 | path = InchwormTests;
108 | sourceTree = "";
109 | };
110 | 5FFD2417235E37FA00602F3A /* Source */ = {
111 | isa = PBXGroup;
112 | children = (
113 | 5F732BFD23578E57008B53DA /* Inchworm.swift */,
114 | 5FE0BBA3235DFB10007EFB8E /* Slider.swift */,
115 | 5F732C0323578F67008B53DA /* SlideRuler.swift */,
116 | F2B4FDA92A3CA65900667F22 /* SlideRulerPositionHelper.swift */,
117 | 5F732C0123578ED7008B53DA /* ProcessIndicatorContainer.swift */,
118 | 5FE0BBA4235DFB10007EFB8E /* ProcessIndicatorView.swift */,
119 | F2058F2E2A42A383008DEBAA /* ProcessIndicatorViewModel.swift */,
120 | );
121 | path = Source;
122 | sourceTree = "";
123 | };
124 | /* End PBXGroup section */
125 |
126 | /* Begin PBXHeadersBuildPhase section */
127 | 5F732BDE23578DF2008B53DA /* Headers */ = {
128 | isa = PBXHeadersBuildPhase;
129 | buildActionMask = 2147483647;
130 | files = (
131 | 5F732BF423578DF2008B53DA /* Inchworm.h in Headers */,
132 | );
133 | runOnlyForDeploymentPostprocessing = 0;
134 | };
135 | /* End PBXHeadersBuildPhase section */
136 |
137 | /* Begin PBXNativeTarget section */
138 | 5F732BE223578DF2008B53DA /* Inchworm */ = {
139 | isa = PBXNativeTarget;
140 | buildConfigurationList = 5F732BF723578DF2008B53DA /* Build configuration list for PBXNativeTarget "Inchworm" */;
141 | buildPhases = (
142 | 5F732BDE23578DF2008B53DA /* Headers */,
143 | 5F732BDF23578DF2008B53DA /* Sources */,
144 | 5F732BE023578DF2008B53DA /* Frameworks */,
145 | 5F732BE123578DF2008B53DA /* Resources */,
146 | );
147 | buildRules = (
148 | );
149 | dependencies = (
150 | );
151 | name = Inchworm;
152 | productName = Inchworm;
153 | productReference = 5F732BE323578DF2008B53DA /* Inchworm.framework */;
154 | productType = "com.apple.product-type.framework";
155 | };
156 | 5F732BEB23578DF2008B53DA /* InchwormTests */ = {
157 | isa = PBXNativeTarget;
158 | buildConfigurationList = 5F732BFA23578DF2008B53DA /* Build configuration list for PBXNativeTarget "InchwormTests" */;
159 | buildPhases = (
160 | 5F732BE823578DF2008B53DA /* Sources */,
161 | 5F732BE923578DF2008B53DA /* Frameworks */,
162 | 5F732BEA23578DF2008B53DA /* Resources */,
163 | );
164 | buildRules = (
165 | );
166 | dependencies = (
167 | 5F732BEF23578DF2008B53DA /* PBXTargetDependency */,
168 | );
169 | name = InchwormTests;
170 | productName = InchwormTests;
171 | productReference = 5F732BEC23578DF2008B53DA /* InchwormTests.xctest */;
172 | productType = "com.apple.product-type.bundle.unit-test";
173 | };
174 | /* End PBXNativeTarget section */
175 |
176 | /* Begin PBXProject section */
177 | 5F732BDA23578DF2008B53DA /* Project object */ = {
178 | isa = PBXProject;
179 | attributes = {
180 | LastSwiftUpdateCheck = 1110;
181 | LastUpgradeCheck = 1110;
182 | ORGANIZATIONNAME = Echo;
183 | TargetAttributes = {
184 | 5F732BE223578DF2008B53DA = {
185 | CreatedOnToolsVersion = 11.1;
186 | LastSwiftMigration = 1110;
187 | };
188 | 5F732BEB23578DF2008B53DA = {
189 | CreatedOnToolsVersion = 11.1;
190 | };
191 | };
192 | };
193 | buildConfigurationList = 5F732BDD23578DF2008B53DA /* Build configuration list for PBXProject "Inchworm" */;
194 | compatibilityVersion = "Xcode 9.3";
195 | developmentRegion = en;
196 | hasScannedForEncodings = 0;
197 | knownRegions = (
198 | en,
199 | Base,
200 | );
201 | mainGroup = 5F732BD923578DF2008B53DA;
202 | productRefGroup = 5F732BE423578DF2008B53DA /* Products */;
203 | projectDirPath = "";
204 | projectRoot = "";
205 | targets = (
206 | 5F732BE223578DF2008B53DA /* Inchworm */,
207 | 5F732BEB23578DF2008B53DA /* InchwormTests */,
208 | );
209 | };
210 | /* End PBXProject section */
211 |
212 | /* Begin PBXResourcesBuildPhase section */
213 | 5F732BE123578DF2008B53DA /* Resources */ = {
214 | isa = PBXResourcesBuildPhase;
215 | buildActionMask = 2147483647;
216 | files = (
217 | 5F732C30235942BD008B53DA /* README.md in Resources */,
218 | 5FE0BBA2235D4CDB007EFB8E /* Inchworm.podspec in Resources */,
219 | );
220 | runOnlyForDeploymentPostprocessing = 0;
221 | };
222 | 5F732BEA23578DF2008B53DA /* Resources */ = {
223 | isa = PBXResourcesBuildPhase;
224 | buildActionMask = 2147483647;
225 | files = (
226 | );
227 | runOnlyForDeploymentPostprocessing = 0;
228 | };
229 | /* End PBXResourcesBuildPhase section */
230 |
231 | /* Begin PBXSourcesBuildPhase section */
232 | 5F732BDF23578DF2008B53DA /* Sources */ = {
233 | isa = PBXSourcesBuildPhase;
234 | buildActionMask = 2147483647;
235 | files = (
236 | 5FE0BBA6235DFB10007EFB8E /* ProcessIndicatorView.swift in Sources */,
237 | F2058F2F2A42A383008DEBAA /* ProcessIndicatorViewModel.swift in Sources */,
238 | F2B4FDAA2A3CA65900667F22 /* SlideRulerPositionHelper.swift in Sources */,
239 | 5FE0BBA5235DFB10007EFB8E /* Slider.swift in Sources */,
240 | 5F732BFE23578E57008B53DA /* Inchworm.swift in Sources */,
241 | 5F732C0423578F67008B53DA /* SlideRuler.swift in Sources */,
242 | 5F732C0223578ED7008B53DA /* ProcessIndicatorContainer.swift in Sources */,
243 | );
244 | runOnlyForDeploymentPostprocessing = 0;
245 | };
246 | 5F732BE823578DF2008B53DA /* Sources */ = {
247 | isa = PBXSourcesBuildPhase;
248 | buildActionMask = 2147483647;
249 | files = (
250 | 5F732BF223578DF2008B53DA /* InchwormTests.swift in Sources */,
251 | );
252 | runOnlyForDeploymentPostprocessing = 0;
253 | };
254 | /* End PBXSourcesBuildPhase section */
255 |
256 | /* Begin PBXTargetDependency section */
257 | 5F732BEF23578DF2008B53DA /* PBXTargetDependency */ = {
258 | isa = PBXTargetDependency;
259 | target = 5F732BE223578DF2008B53DA /* Inchworm */;
260 | targetProxy = 5F732BEE23578DF2008B53DA /* PBXContainerItemProxy */;
261 | };
262 | /* End PBXTargetDependency section */
263 |
264 | /* Begin XCBuildConfiguration section */
265 | 5F732BF523578DF2008B53DA /* Debug */ = {
266 | isa = XCBuildConfiguration;
267 | buildSettings = {
268 | ALWAYS_SEARCH_USER_PATHS = NO;
269 | CLANG_ANALYZER_NONNULL = YES;
270 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
271 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
272 | CLANG_CXX_LIBRARY = "libc++";
273 | CLANG_ENABLE_MODULES = YES;
274 | CLANG_ENABLE_OBJC_ARC = YES;
275 | CLANG_ENABLE_OBJC_WEAK = YES;
276 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
277 | CLANG_WARN_BOOL_CONVERSION = YES;
278 | CLANG_WARN_COMMA = YES;
279 | CLANG_WARN_CONSTANT_CONVERSION = YES;
280 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
281 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
282 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
283 | CLANG_WARN_EMPTY_BODY = YES;
284 | CLANG_WARN_ENUM_CONVERSION = YES;
285 | CLANG_WARN_INFINITE_RECURSION = YES;
286 | CLANG_WARN_INT_CONVERSION = YES;
287 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
288 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
289 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
290 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
291 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
292 | CLANG_WARN_STRICT_PROTOTYPES = YES;
293 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
294 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
295 | CLANG_WARN_UNREACHABLE_CODE = YES;
296 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
297 | COPY_PHASE_STRIP = NO;
298 | CURRENT_PROJECT_VERSION = 1;
299 | DEBUG_INFORMATION_FORMAT = dwarf;
300 | ENABLE_STRICT_OBJC_MSGSEND = YES;
301 | ENABLE_TESTABILITY = YES;
302 | GCC_C_LANGUAGE_STANDARD = gnu11;
303 | GCC_DYNAMIC_NO_PIC = NO;
304 | GCC_NO_COMMON_BLOCKS = YES;
305 | GCC_OPTIMIZATION_LEVEL = 0;
306 | GCC_PREPROCESSOR_DEFINITIONS = (
307 | "DEBUG=1",
308 | "$(inherited)",
309 | );
310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
312 | GCC_WARN_UNDECLARED_SELECTOR = YES;
313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
314 | GCC_WARN_UNUSED_FUNCTION = YES;
315 | GCC_WARN_UNUSED_VARIABLE = YES;
316 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
317 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
318 | MTL_FAST_MATH = YES;
319 | ONLY_ACTIVE_ARCH = YES;
320 | SDKROOT = iphoneos;
321 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
322 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
323 | VERSIONING_SYSTEM = "apple-generic";
324 | VERSION_INFO_PREFIX = "";
325 | };
326 | name = Debug;
327 | };
328 | 5F732BF623578DF2008B53DA /* Release */ = {
329 | isa = XCBuildConfiguration;
330 | buildSettings = {
331 | ALWAYS_SEARCH_USER_PATHS = NO;
332 | CLANG_ANALYZER_NONNULL = YES;
333 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
334 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
335 | CLANG_CXX_LIBRARY = "libc++";
336 | CLANG_ENABLE_MODULES = YES;
337 | CLANG_ENABLE_OBJC_ARC = YES;
338 | CLANG_ENABLE_OBJC_WEAK = YES;
339 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
340 | CLANG_WARN_BOOL_CONVERSION = YES;
341 | CLANG_WARN_COMMA = YES;
342 | CLANG_WARN_CONSTANT_CONVERSION = YES;
343 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
344 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
345 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
346 | CLANG_WARN_EMPTY_BODY = YES;
347 | CLANG_WARN_ENUM_CONVERSION = YES;
348 | CLANG_WARN_INFINITE_RECURSION = YES;
349 | CLANG_WARN_INT_CONVERSION = YES;
350 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
351 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
352 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
353 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
354 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
355 | CLANG_WARN_STRICT_PROTOTYPES = YES;
356 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
357 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
358 | CLANG_WARN_UNREACHABLE_CODE = YES;
359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
360 | COPY_PHASE_STRIP = NO;
361 | CURRENT_PROJECT_VERSION = 1;
362 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
363 | ENABLE_NS_ASSERTIONS = NO;
364 | ENABLE_STRICT_OBJC_MSGSEND = YES;
365 | GCC_C_LANGUAGE_STANDARD = gnu11;
366 | GCC_NO_COMMON_BLOCKS = YES;
367 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
368 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
369 | GCC_WARN_UNDECLARED_SELECTOR = YES;
370 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
371 | GCC_WARN_UNUSED_FUNCTION = YES;
372 | GCC_WARN_UNUSED_VARIABLE = YES;
373 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
374 | MTL_ENABLE_DEBUG_INFO = NO;
375 | MTL_FAST_MATH = YES;
376 | SDKROOT = iphoneos;
377 | SWIFT_COMPILATION_MODE = wholemodule;
378 | SWIFT_OPTIMIZATION_LEVEL = "-O";
379 | VALIDATE_PRODUCT = YES;
380 | VERSIONING_SYSTEM = "apple-generic";
381 | VERSION_INFO_PREFIX = "";
382 | };
383 | name = Release;
384 | };
385 | 5F732BF823578DF2008B53DA /* Debug */ = {
386 | isa = XCBuildConfiguration;
387 | buildSettings = {
388 | CLANG_ENABLE_MODULES = YES;
389 | CODE_SIGN_STYLE = Automatic;
390 | DEFINES_MODULE = YES;
391 | DEVELOPMENT_TEAM = 3V26Q55U3S;
392 | DYLIB_COMPATIBILITY_VERSION = 1;
393 | DYLIB_CURRENT_VERSION = 1;
394 | DYLIB_INSTALL_NAME_BASE = "@rpath";
395 | INFOPLIST_FILE = Inchworm/Info.plist;
396 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
397 | LD_RUNPATH_SEARCH_PATHS = (
398 | "$(inherited)",
399 | "@executable_path/Frameworks",
400 | "@loader_path/Frameworks",
401 | );
402 | MARKETING_VERSION = 1.1.0;
403 | PRODUCT_BUNDLE_IDENTIFIER = com.echo.Inchworm;
404 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
405 | SKIP_INSTALL = YES;
406 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
407 | SWIFT_VERSION = 5.0;
408 | TARGETED_DEVICE_FAMILY = "1,2";
409 | };
410 | name = Debug;
411 | };
412 | 5F732BF923578DF2008B53DA /* Release */ = {
413 | isa = XCBuildConfiguration;
414 | buildSettings = {
415 | CLANG_ENABLE_MODULES = YES;
416 | CODE_SIGN_STYLE = Automatic;
417 | DEFINES_MODULE = YES;
418 | DEVELOPMENT_TEAM = 3V26Q55U3S;
419 | DYLIB_COMPATIBILITY_VERSION = 1;
420 | DYLIB_CURRENT_VERSION = 1;
421 | DYLIB_INSTALL_NAME_BASE = "@rpath";
422 | INFOPLIST_FILE = Inchworm/Info.plist;
423 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
424 | LD_RUNPATH_SEARCH_PATHS = (
425 | "$(inherited)",
426 | "@executable_path/Frameworks",
427 | "@loader_path/Frameworks",
428 | );
429 | MARKETING_VERSION = 1.1.0;
430 | PRODUCT_BUNDLE_IDENTIFIER = com.echo.Inchworm;
431 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
432 | SKIP_INSTALL = YES;
433 | SWIFT_VERSION = 5.0;
434 | TARGETED_DEVICE_FAMILY = "1,2";
435 | };
436 | name = Release;
437 | };
438 | 5F732BFB23578DF2008B53DA /* Debug */ = {
439 | isa = XCBuildConfiguration;
440 | buildSettings = {
441 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
442 | CODE_SIGN_STYLE = Automatic;
443 | DEVELOPMENT_TEAM = 3V26Q55U3S;
444 | INFOPLIST_FILE = InchwormTests/Info.plist;
445 | LD_RUNPATH_SEARCH_PATHS = (
446 | "$(inherited)",
447 | "@executable_path/Frameworks",
448 | "@loader_path/Frameworks",
449 | );
450 | PRODUCT_BUNDLE_IDENTIFIER = com.echo.InchwormTests;
451 | PRODUCT_NAME = "$(TARGET_NAME)";
452 | SWIFT_VERSION = 5.0;
453 | TARGETED_DEVICE_FAMILY = "1,2";
454 | };
455 | name = Debug;
456 | };
457 | 5F732BFC23578DF2008B53DA /* Release */ = {
458 | isa = XCBuildConfiguration;
459 | buildSettings = {
460 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
461 | CODE_SIGN_STYLE = Automatic;
462 | DEVELOPMENT_TEAM = 3V26Q55U3S;
463 | INFOPLIST_FILE = InchwormTests/Info.plist;
464 | LD_RUNPATH_SEARCH_PATHS = (
465 | "$(inherited)",
466 | "@executable_path/Frameworks",
467 | "@loader_path/Frameworks",
468 | );
469 | PRODUCT_BUNDLE_IDENTIFIER = com.echo.InchwormTests;
470 | PRODUCT_NAME = "$(TARGET_NAME)";
471 | SWIFT_VERSION = 5.0;
472 | TARGETED_DEVICE_FAMILY = "1,2";
473 | };
474 | name = Release;
475 | };
476 | /* End XCBuildConfiguration section */
477 |
478 | /* Begin XCConfigurationList section */
479 | 5F732BDD23578DF2008B53DA /* Build configuration list for PBXProject "Inchworm" */ = {
480 | isa = XCConfigurationList;
481 | buildConfigurations = (
482 | 5F732BF523578DF2008B53DA /* Debug */,
483 | 5F732BF623578DF2008B53DA /* Release */,
484 | );
485 | defaultConfigurationIsVisible = 0;
486 | defaultConfigurationName = Release;
487 | };
488 | 5F732BF723578DF2008B53DA /* Build configuration list for PBXNativeTarget "Inchworm" */ = {
489 | isa = XCConfigurationList;
490 | buildConfigurations = (
491 | 5F732BF823578DF2008B53DA /* Debug */,
492 | 5F732BF923578DF2008B53DA /* Release */,
493 | );
494 | defaultConfigurationIsVisible = 0;
495 | defaultConfigurationName = Release;
496 | };
497 | 5F732BFA23578DF2008B53DA /* Build configuration list for PBXNativeTarget "InchwormTests" */ = {
498 | isa = XCConfigurationList;
499 | buildConfigurations = (
500 | 5F732BFB23578DF2008B53DA /* Debug */,
501 | 5F732BFC23578DF2008B53DA /* Release */,
502 | );
503 | defaultConfigurationIsVisible = 0;
504 | defaultConfigurationName = Release;
505 | };
506 | /* End XCConfigurationList section */
507 | };
508 | rootObject = 5F732BDA23578DF2008B53DA /* Project object */;
509 | }
510 |
--------------------------------------------------------------------------------