├── 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 | Inchworm 3 |

4 | 5 |

6 | swift 5.0 badge 7 | platform iOS badge 8 | license MIT badge 9 |

10 | 11 | # Inchworm 12 | 13 | A slider for adjusting values just like the one in Photo.app in iOS 13 14 | 15 |

16 | Inchworm 17 | Inchworm 18 | Inchworm 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 | --------------------------------------------------------------------------------