diff --git a/CHANGELOG.md b/CHANGELOG.md
index f280ba8..2c616b8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# CHANGELOG
+## [0.1.1] - 2024-03-17
+
+### Changed
+- Updated changelog
+
+## [0.1.0] - 2024-03-17
+
+### Added
+- Added new configurations for direction-specific animations
+- Refined animation transitions when changing directions for a more fluid user experience
+- SliderDelegate now supports Swift Concurrency for asynchronous event handling
+
+### Changed
+- Rendering of changes is now synchronized with the GPU using CADisplayLink for smoother visual updates.
+- Removed support for Interface Builder to streamline codebase and improve programmability.
+
## [0.0.9] - 2024-03-14
### Fixed
diff --git a/README.md b/README.md
index cad6fa1..1f4b104 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,14 @@
- Swift 5+
## Preview
-
+
+ Preview
+
+
+
+
+
+
## Installation
@@ -29,7 +36,7 @@ Once you have your Swift package set up, adding Slider as a dependency is as eas
```swift
dependencies: [
- .package(url: "https://github.com/Ramiz69/Slider.git", .upToNextMajor(from: "0.0.9"))
+ .package(url: "https://github.com/Ramiz69/Slider.git", .upToNextMajor(from: "0.1.0"))
]
```
diff --git a/RKSlider.podspec b/RKSlider.podspec
index 2ec9076..6d854db 100644
--- a/RKSlider.podspec
+++ b/RKSlider.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |spec|
spec.name = 'RKSlider'
- spec.version = '0.0.9'
+ spec.version = '0.1.1'
spec.summary = 'A CocoaPods library written in Swift'
spec.description = <<-DESC
diff --git a/RKSlider/0.1.0/RKSlider.podspec b/RKSlider/0.1.0/RKSlider.podspec
new file mode 100644
index 0000000..4048b4b
--- /dev/null
+++ b/RKSlider/0.1.0/RKSlider.podspec
@@ -0,0 +1,26 @@
+Pod::Spec.new do |spec|
+
+ spec.name = 'RKSlider'
+ spec.version = '0.1.0'
+ spec.summary = 'A CocoaPods library written in Swift'
+
+ spec.description = <<-DESC
+This CocoaPods library helps you create application with the best slider.
+ DESC
+
+ spec.homepage = 'https://github.com/Ramiz69/Slider'
+
+ spec.license = 'MIT'
+
+ spec.author = { 'Ramiz Kichibekov' => 'ramiz161@icloud.com' }
+ spec.social_media_url = 'https://t.me/Ramiz69'
+
+ spec.ios.deployment_target = "14.0"
+
+ spec.source = { :git => 'https://github.com/Ramiz69/Slider.git', :tag => spec.version }
+
+ spec.swift_version = ['5.0', '5.9']
+
+ spec.source_files = 'Sources/*.swift', 'Sources/**/*.swift'
+
+end
diff --git a/Screenshots/leftToRightCustom.png b/Screenshots/leftToRightCustom.png
new file mode 100644
index 0000000..4b22a6b
Binary files /dev/null and b/Screenshots/leftToRightCustom.png differ
diff --git a/Screenshots/leftToRightDefault.png b/Screenshots/leftToRightDefault.png
new file mode 100644
index 0000000..3cc0406
Binary files /dev/null and b/Screenshots/leftToRightDefault.png differ
diff --git a/Screenshots/preference.png b/Screenshots/preference.png
new file mode 100644
index 0000000..51a58ab
Binary files /dev/null and b/Screenshots/preference.png differ
diff --git a/Screenshots/rightToLeftDefault.png b/Screenshots/rightToLeftDefault.png
new file mode 100644
index 0000000..602d2a4
Binary files /dev/null and b/Screenshots/rightToLeftDefault.png differ
diff --git a/Slider.xcodeproj/project.pbxproj b/Slider.xcodeproj/project.pbxproj
index 618f7b0..6567d95 100644
--- a/Slider.xcodeproj/project.pbxproj
+++ b/Slider.xcodeproj/project.pbxproj
@@ -16,14 +16,21 @@
4B2EB6F123C1E5AA00FB91AD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B2EB6EF23C1E5AA00FB91AD /* LaunchScreen.storyboard */; };
4B889B692433375D002FCE02 /* CodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B889B682433375D002FCE02 /* CodeViewController.swift */; };
4B889B7624333E7C002FCE02 /* CodeViewController+SliderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B889B7524333E7C002FCE02 /* CodeViewController+SliderDelegate.swift */; };
+ 4B8DAE962BA6925A005CFA82 /* Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DAE952BA6925A005CFA82 /* Animation.swift */; };
+ 4B8DAE982BA6956B005CFA82 /* TrackConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DAE972BA6956B005CFA82 /* TrackConfiguration.swift */; };
+ 4B8DAE9A2BA696A8005CFA82 /* ThumbConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DAE992BA696A8005CFA82 /* ThumbConfiguration.swift */; };
+ 4B8DAE9C2BA69A69005CFA82 /* RangeEndpointsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DAE9B2BA69A69005CFA82 /* RangeEndpointsConfiguration.swift */; };
+ 4B8DAE9F2BA6A056005CFA82 /* PreferenceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DAE9E2BA6A056005CFA82 /* PreferenceManager.swift */; };
+ 4B8DAEA12BA6A5E9005CFA82 /* CodeViewController+UIColorPickerViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DAEA02BA6A5E9005CFA82 /* CodeViewController+UIColorPickerViewControllerDelegate.swift */; };
+ 4B8DAEA42BA6AE54005CFA82 /* ThumbLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DAEA22BA6ACFF005CFA82 /* ThumbLayer.swift */; };
+ 4B8DAEA62BA6B445005CFA82 /* TextLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8DAEA52BA6B445005CFA82 /* TextLayer.swift */; };
4BF628A12BA2B38D0093637E /* HapticConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF628922BA2B38D0093637E /* HapticConfiguration.swift */; };
- 4BF628A22BA2B38D0093637E /* DirectionEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF628942BA2B38D0093637E /* DirectionEnum.swift */; };
+ 4BF628A22BA2B38D0093637E /* Direction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF628942BA2B38D0093637E /* Direction.swift */; };
4BF628A32BA2B38D0093637E /* Slider+UITouch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF628962BA2B38D0093637E /* Slider+UITouch.swift */; };
4BF628A42BA2B38D0093637E /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF628972BA2B38D0093637E /* String.swift */; };
- 4BF628A52BA2B38D0093637E /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF628992BA2B38D0093637E /* Protocols.swift */; };
+ 4BF628A52BA2B38D0093637E /* SliderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF628992BA2B38D0093637E /* SliderDelegate.swift */; };
4BF628A72BA2B38D0093637E /* Slider.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BF6289C2BA2B38D0093637E /* Slider.h */; };
4BF628A82BA2B38D0093637E /* Slider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF6289D2BA2B38D0093637E /* Slider.swift */; };
- 4BF628A92BA2B38D0093637E /* SliderTextLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF6289E2BA2B38D0093637E /* SliderTextLayer.swift */; };
4BF628AA2BA2B38D0093637E /* SliderTrackLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF6289F2BA2B38D0093637E /* SliderTrackLayer.swift */; };
/* End PBXBuildFile section */
@@ -62,15 +69,22 @@
4B2EB6F223C1E5AA00FB91AD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
4B889B682433375D002FCE02 /* CodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeViewController.swift; sourceTree = ""; };
4B889B7524333E7C002FCE02 /* CodeViewController+SliderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CodeViewController+SliderDelegate.swift"; sourceTree = ""; };
+ 4B8DAE952BA6925A005CFA82 /* Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Animation.swift; sourceTree = ""; };
+ 4B8DAE972BA6956B005CFA82 /* TrackConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackConfiguration.swift; sourceTree = ""; };
+ 4B8DAE992BA696A8005CFA82 /* ThumbConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbConfiguration.swift; sourceTree = ""; };
+ 4B8DAE9B2BA69A69005CFA82 /* RangeEndpointsConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeEndpointsConfiguration.swift; sourceTree = ""; };
+ 4B8DAE9E2BA6A056005CFA82 /* PreferenceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceManager.swift; sourceTree = ""; };
+ 4B8DAEA02BA6A5E9005CFA82 /* CodeViewController+UIColorPickerViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CodeViewController+UIColorPickerViewControllerDelegate.swift"; sourceTree = ""; };
+ 4B8DAEA22BA6ACFF005CFA82 /* ThumbLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbLayer.swift; sourceTree = ""; };
+ 4B8DAEA52BA6B445005CFA82 /* TextLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLayer.swift; sourceTree = ""; };
4BF628922BA2B38D0093637E /* HapticConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HapticConfiguration.swift; sourceTree = ""; };
- 4BF628942BA2B38D0093637E /* DirectionEnum.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectionEnum.swift; sourceTree = ""; };
+ 4BF628942BA2B38D0093637E /* Direction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Direction.swift; sourceTree = ""; };
4BF628962BA2B38D0093637E /* Slider+UITouch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Slider+UITouch.swift"; sourceTree = ""; };
4BF628972BA2B38D0093637E /* String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; };
- 4BF628992BA2B38D0093637E /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; };
+ 4BF628992BA2B38D0093637E /* SliderDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderDelegate.swift; sourceTree = ""; };
4BF6289B2BA2B38D0093637E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
4BF6289C2BA2B38D0093637E /* Slider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Slider.h; sourceTree = ""; };
4BF6289D2BA2B38D0093637E /* Slider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Slider.swift; sourceTree = ""; };
- 4BF6289E2BA2B38D0093637E /* SliderTextLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderTextLayer.swift; sourceTree = ""; };
4BF6289F2BA2B38D0093637E /* SliderTrackLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderTrackLayer.swift; sourceTree = ""; };
/* End PBXFileReference section */
@@ -115,6 +129,7 @@
4B2EB6E323C1E5AA00FB91AD /* Slider_Example */ = {
isa = PBXGroup;
children = (
+ 4B8DAE9D2BA6A04B005CFA82 /* Manager */,
4B889B6A24333B9F002FCE02 /* Application */,
4B889B6B24333BAB002FCE02 /* Flows */,
4B889B6F24333BC8002FCE02 /* Supporting Files */,
@@ -186,14 +201,26 @@
isa = PBXGroup;
children = (
4B889B7524333E7C002FCE02 /* CodeViewController+SliderDelegate.swift */,
+ 4B8DAEA02BA6A5E9005CFA82 /* CodeViewController+UIColorPickerViewControllerDelegate.swift */,
);
path = Extensions;
sourceTree = "";
};
+ 4B8DAE9D2BA6A04B005CFA82 /* Manager */ = {
+ isa = PBXGroup;
+ children = (
+ 4B8DAE9E2BA6A056005CFA82 /* PreferenceManager.swift */,
+ );
+ path = Manager;
+ sourceTree = "";
+ };
4BF628932BA2B38D0093637E /* Configurations */ = {
isa = PBXGroup;
children = (
4BF628922BA2B38D0093637E /* HapticConfiguration.swift */,
+ 4B8DAE972BA6956B005CFA82 /* TrackConfiguration.swift */,
+ 4B8DAE992BA696A8005CFA82 /* ThumbConfiguration.swift */,
+ 4B8DAE9B2BA69A69005CFA82 /* RangeEndpointsConfiguration.swift */,
);
path = Configurations;
sourceTree = "";
@@ -201,7 +228,8 @@
4BF628952BA2B38D0093637E /* Enums */ = {
isa = PBXGroup;
children = (
- 4BF628942BA2B38D0093637E /* DirectionEnum.swift */,
+ 4BF628942BA2B38D0093637E /* Direction.swift */,
+ 4B8DAE952BA6925A005CFA82 /* Animation.swift */,
);
path = Enums;
sourceTree = "";
@@ -218,7 +246,7 @@
4BF6289A2BA2B38D0093637E /* Protocols */ = {
isa = PBXGroup;
children = (
- 4BF628992BA2B38D0093637E /* Protocols.swift */,
+ 4BF628992BA2B38D0093637E /* SliderDelegate.swift */,
);
path = Protocols;
sourceTree = "";
@@ -233,8 +261,9 @@
4BF6289B2BA2B38D0093637E /* Info.plist */,
4BF6289C2BA2B38D0093637E /* Slider.h */,
4BF6289D2BA2B38D0093637E /* Slider.swift */,
- 4BF6289E2BA2B38D0093637E /* SliderTextLayer.swift */,
4BF6289F2BA2B38D0093637E /* SliderTrackLayer.swift */,
+ 4B8DAEA22BA6ACFF005CFA82 /* ThumbLayer.swift */,
+ 4B8DAEA52BA6B445005CFA82 /* TextLayer.swift */,
);
path = Sources;
sourceTree = "";
@@ -358,13 +387,18 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 4BF628A52BA2B38D0093637E /* Protocols.swift in Sources */,
+ 4BF628A52BA2B38D0093637E /* SliderDelegate.swift in Sources */,
4BF628A32BA2B38D0093637E /* Slider+UITouch.swift in Sources */,
- 4BF628A22BA2B38D0093637E /* DirectionEnum.swift in Sources */,
+ 4BF628A22BA2B38D0093637E /* Direction.swift in Sources */,
+ 4B8DAE9C2BA69A69005CFA82 /* RangeEndpointsConfiguration.swift in Sources */,
4BF628A42BA2B38D0093637E /* String.swift in Sources */,
- 4BF628A92BA2B38D0093637E /* SliderTextLayer.swift in Sources */,
+ 4B8DAE962BA6925A005CFA82 /* Animation.swift in Sources */,
+ 4B8DAE9A2BA696A8005CFA82 /* ThumbConfiguration.swift in Sources */,
4BF628A82BA2B38D0093637E /* Slider.swift in Sources */,
+ 4B8DAEA62BA6B445005CFA82 /* TextLayer.swift in Sources */,
4BF628AA2BA2B38D0093637E /* SliderTrackLayer.swift in Sources */,
+ 4B8DAE982BA6956B005CFA82 /* TrackConfiguration.swift in Sources */,
+ 4B8DAEA42BA6AE54005CFA82 /* ThumbLayer.swift in Sources */,
4BF628A12BA2B38D0093637E /* HapticConfiguration.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -373,6 +407,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 4B8DAE9F2BA6A056005CFA82 /* PreferenceManager.swift in Sources */,
+ 4B8DAEA12BA6A5E9005CFA82 /* CodeViewController+UIColorPickerViewControllerDelegate.swift in Sources */,
4B889B692433375D002FCE02 /* CodeViewController.swift in Sources */,
4B2EB6E523C1E5AA00FB91AD /* AppDelegate.swift in Sources */,
4B889B7624333E7C002FCE02 /* CodeViewController+SliderDelegate.swift in Sources */,
diff --git a/Slider.xcodeproj/project.xcworkspace/xcuserdata/ramizkichibekov.xcuserdatad/UserInterfaceState.xcuserstate b/Slider.xcodeproj/project.xcworkspace/xcuserdata/ramizkichibekov.xcuserdatad/UserInterfaceState.xcuserstate
index 86b797a..c64492d 100644
Binary files a/Slider.xcodeproj/project.xcworkspace/xcuserdata/ramizkichibekov.xcuserdatad/UserInterfaceState.xcuserstate and b/Slider.xcodeproj/project.xcworkspace/xcuserdata/ramizkichibekov.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/Slider_Example/Application/AppDelegate.swift b/Slider_Example/Application/AppDelegate.swift
index 75ca3d7..04c6ffa 100644
--- a/Slider_Example/Application/AppDelegate.swift
+++ b/Slider_Example/Application/AppDelegate.swift
@@ -1,37 +1,47 @@
//
// AppDelegate.swift
-// Slider_Example
//
-// Created by Рамиз Кичибеков on 05.01.2020.
-// Copyright © 2020 Ramiz Kichibekov. All rights reserved.
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
//
import UIKit
@UIApplicationMain
-class AppDelegate: UIResponder, UIApplicationDelegate {
-
+final class AppDelegate: UIResponder, UIApplicationDelegate {
-
- func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
- // Override point for customization after application launch.
+ func application(_ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
// MARK: UISceneSession Lifecycle
- func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
- // Called when a new scene session is being created.
- // Use this method to select a configuration to create the new scene with.
+ func application(_ application: UIApplication,
+ configurationForConnecting connectingSceneSession: UISceneSession,
+ options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
- func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
- // Called when the user discards a scene session.
- // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
- // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
+ func application(_ application: UIApplication,
+ didDiscardSceneSessions sceneSessions: Set) {
}
-
-
+
}
-
diff --git a/Slider_Example/Application/SceneDelegate.swift b/Slider_Example/Application/SceneDelegate.swift
index 8277e42..09635fc 100644
--- a/Slider_Example/Application/SceneDelegate.swift
+++ b/Slider_Example/Application/SceneDelegate.swift
@@ -1,53 +1,35 @@
//
// SceneDelegate.swift
-// Slider_Example
//
-// Created by Рамиз Кичибеков on 05.01.2020.
-// Copyright © 2020 Ramiz Kichibekov. All rights reserved.
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
//
import UIKit
-class SceneDelegate: UIResponder, UIWindowSceneDelegate {
+final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
-
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
- // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
- // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
- // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
- guard let _ = (scene as? UIWindowScene) else { return }
- }
-
- func sceneDidDisconnect(_ scene: UIScene) {
- // Called as the scene is being released by the system.
- // This occurs shortly after the scene enters the background, or when its session is discarded.
- // Release any resources associated with this scene that can be re-created the next time the scene connects.
- // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
- }
-
- func sceneDidBecomeActive(_ scene: UIScene) {
- // Called when the scene has moved from an inactive state to an active state.
- // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
+
}
- func sceneWillResignActive(_ scene: UIScene) {
- // Called when the scene will move from an active state to an inactive state.
- // This may occur due to temporary interruptions (ex. an incoming phone call).
- }
-
- func sceneWillEnterForeground(_ scene: UIScene) {
- // Called as the scene transitions from the background to the foreground.
- // Use this method to undo the changes made on entering the background.
- }
-
- func sceneDidEnterBackground(_ scene: UIScene) {
- // Called as the scene transitions from the foreground to the background.
- // Use this method to save data, release shared resources, and store enough scene-specific state information
- // to restore the scene back to its current state.
- }
-
-
}
-
diff --git a/Slider_Example/Flows/Main/Controllers/CodeViewController.swift b/Slider_Example/Flows/Main/Controllers/CodeViewController.swift
index 813dd52..84eacf2 100644
--- a/Slider_Example/Flows/Main/Controllers/CodeViewController.swift
+++ b/Slider_Example/Flows/Main/Controllers/CodeViewController.swift
@@ -1,9 +1,25 @@
//
// CodeViewController.swift
-// Slider_Example
//
-// Created by Рамиз Кичибеков on 31.03.2020.
-// Copyright © 2020 Ramiz Kichibekov. All rights reserved.
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
//
import UIKit
@@ -14,13 +30,30 @@ final class CodeViewController: UIViewController {
// MARK: - Outlets
@IBOutlet var segmentControl: UISegmentedControl!
- @IBOutlet var reachSwitch: UISwitch!
- @IBOutlet var valueSwitch: UISwitch!
- @IBOutlet var directionSwitch: UISwitch!
+ @IBOutlet var preferenceBarButton: UIBarButtonItem!
// MARK: - Properties
+ enum ColorPickerType {
+ case thumb
+ case track(Track)
+ case endpoint(Endpoint)
+
+ enum Endpoint {
+ case minimum
+ case maximum
+ }
+
+ enum Track {
+ case min
+ case max
+ case reverseMin
+ }
+ }
+
private let slider = Slider()
+ private(set) var preference = PreferenceManager()
+ private(set) var selectedColorPickerType: ColorPickerType!
// MARK: - Life cycle
@@ -28,17 +61,55 @@ final class CodeViewController: UIViewController {
super.viewDidLoad()
configureController()
+ configurePreferenceMenu()
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+
+ // let offset: CGFloat = 16
+ // slider.frame = CGRect(x: offset,
+ // y: view.safeAreaInsets.top,
+ // width: view.bounds.width - 2 * offset,
+ // height: slider.trackHeight)
+ }
+
+ // MARK: Public methods
+
+ func configureMinimumEndpoint() {
+ slider.minimumEndpointConfiguration = .init(foregroundColor: preference.minimumEndpointPreference.foregroundColor,
+ aligmentMode: preference.minimumEndpointPreference.aligmentMode)
+ configurePreferenceMenu()
+ }
+
+ func configureMaximumEndpoint() {
+ slider.maximumEndpointConfiguration = .init(foregroundColor: preference.maximumEndpointPreference.foregroundColor,
+ aligmentMode: preference.maximumEndpointPreference.aligmentMode)
+ configurePreferenceMenu()
+ }
+
+ func configureThumb() {
+ slider.thumbConfiguration = .init(backgroundColor: preference.thumbPreference.backgroundColor)
+ configurePreferenceMenu()
+ }
+
+ func configureTrack() {
+ slider.trackConfiguration = .init(maxColor: preference.trackPreference.maxColor,
+ minColor: preference.trackPreference.minColor,
+ reverseMinColor: preference.trackPreference.reverseMinColor)
+ configurePreferenceMenu()
}
// MARK: Private methods
private func configureController() {
- // slider.delegate = self
- slider.direction = DirectionEnum(withValue: segmentControl.selectedSegmentIndex)
+// slider.delegate = self
slider.maximum = 1500
slider.minimum = .zero
slider.value = .zero
+ slider.animationStyle = .default
view.addSubview(slider)
+ slider.translatesAutoresizingMaskIntoConstraints = false
let layoutMarginsGuide = view.layoutMarginsGuide
let offset: CGFloat = 16
let constraints = [slider.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: offset),
@@ -48,29 +119,169 @@ final class CodeViewController: UIViewController {
}
private func configureHaptic() {
- slider.hapticConfiguration = .init(reachLimitValueHapticEnabled: reachSwitch.isOn,
- changeValueHapticEnabled: valueSwitch.isOn,
- changeDirectionHapticEnabled: directionSwitch.isOn,
+ slider.hapticConfiguration = .init(reachLimitValueHapticEnabled: preference.hapticPreference.reachMinMaxValues,
+ changeValueHapticEnabled: preference.hapticPreference.valueChanges,
+ changeDirectionHapticEnabled: preference.hapticPreference.directionChanges,
reachImpactGeneratorStyle: .medium,
changeValueImpactGeneratorStyle: .light)
+ configurePreferenceMenu()
}
- // MARK: - Actions
+ private func resetSlider() {
+ slider.hapticConfiguration = .init(reachLimitValueHapticEnabled: preference.hapticPreference.reachMinMaxValues,
+ changeValueHapticEnabled: preference.hapticPreference.valueChanges,
+ changeDirectionHapticEnabled: preference.hapticPreference.directionChanges,
+ reachImpactGeneratorStyle: .medium,
+ changeValueImpactGeneratorStyle: .light)
+ slider.thumbConfiguration = .init()
+ slider.maximumEndpointConfiguration = .init()
+ slider.minimumEndpointConfiguration = .init()
+ }
- @IBAction private func didChangeSegment(_ sender: UISegmentedControl) {
- slider.direction = DirectionEnum(withValue: sender.selectedSegmentIndex)
+ private func configurePreferenceMenu() {
+ let reset = UIAction(title: "Reset") { [weak self] _ in
+ self?.preference = PreferenceManager.reset()
+ self?.resetSlider()
+ }
+ let menu = UIMenu(title: "Preference",
+ image: UIImage(systemName: "gear"),
+ children: [configureAnimationMenu(),
+ configureTrackMenu(),
+ configureHapticMenu(),
+ configureEndpointMenu(),
+ configureThumbMenu(),
+ reset])
+
+ preferenceBarButton.menu = menu
}
- @IBAction private func didChangeReachValue(_ sender: UISwitch) {
- configureHaptic()
+ private func configureAnimationMenu() -> UIMenu {
+ let styles: [AnimationStyle] = [.none, .default]
+ var animations = [UIAction]()
+ styles.forEach { style in
+ let action = UIAction(title: "\(style)", state: style == slider.animationStyle ? .on : .off) { [weak self] _ in
+ self?.slider.animationStyle = style
+ self?.configurePreferenceMenu()
+ }
+ animations.append(action)
+ }
+
+ return UIMenu(title: "Animation Direction Change", children: animations)
}
- @IBAction private func didChangeValue(_ sender: UISwitch) {
- configureHaptic()
+ private func configureTrackMenu() -> UIMenu {
+ let minColorAction = UIAction(title: "Minimum Color") { [weak self] _ in
+ self?.selectedColorPickerType = .track(.min)
+ self?.presentColorPicker()
+ }
+ let maxColorAction = UIAction(title: "Maximum Color") { [weak self] _ in
+ self?.selectedColorPickerType = .track(.max)
+ self?.presentColorPicker()
+ }
+ let reverseMinColorAction = UIAction(title: "Reverse Minimum Color") { [weak self] _ in
+ self?.selectedColorPickerType = .track(.reverseMin)
+ self?.presentColorPicker()
+ }
+ return UIMenu(title: "Track", children: [minColorAction, maxColorAction, reverseMinColorAction])
}
- @IBAction private func didChangeDirectionValue(_ sender: UISwitch) {
- configureHaptic()
+ private func configureThumbMenu() -> UIMenu {
+ let backgroundColor = UIAction(title: "Background") { [weak self] _ in
+ self?.selectedColorPickerType = .thumb
+ self?.presentColorPicker()
+ }
+
+ return UIMenu(title: "Thumb", children: [backgroundColor])
}
+ private func configureHapticMenu() -> UIMenu {
+ let hapticPreference = preference.hapticPreference
+ let reachAction = UIAction(title: "Reach Min/Max values",
+ state: hapticPreference.reachMinMaxValues ? .on : .off) { [weak self] _ in
+ hapticPreference.reachMinMaxValues.toggle()
+ self?.configureHaptic()
+ }
+ let valueChangeAction = UIAction(title: "Value changes",
+ state: hapticPreference.valueChanges ? .on : .off) { [weak self] _ in
+ hapticPreference.valueChanges.toggle()
+ self?.configureHaptic()
+ }
+ let directionChangeAction = UIAction(title: "Direction changes",
+ state: hapticPreference.directionChanges ? .on : .off) { [weak self] _ in
+ hapticPreference.directionChanges.toggle()
+ self?.configureHaptic()
+ }
+
+ return UIMenu(title: "Haptic",
+ image: UIImage(systemName: "iphone.gen3.radiowaves.left.and.right"),
+ children: [reachAction, valueChangeAction, directionChangeAction])
+ }
+
+ private func configureEndpointMenu() -> UIMenu {
+ let maximumEndpointPreference = preference.maximumEndpointPreference
+ let maximumTextColorAction = UIAction(title: "Change color") { [weak self] _ in
+ self?.selectedColorPickerType = .endpoint(.maximum)
+ self?.presentColorPicker()
+ }
+
+ let minimumEndpointPreference = preference.minimumEndpointPreference
+ let minimumTextColorAction = UIAction(title: "Change color") { [weak self] _ in
+ self?.selectedColorPickerType = .endpoint(.minimum)
+ self?.presentColorPicker()
+ }
+
+
+ let minimumAligmentMenu = UIMenu(title: "Aligment",
+ image: UIImage(systemName: "text.alignleft"),
+ children: configureAligmentActions(for: minimumEndpointPreference))
+ let maximumAligmentMenu = UIMenu(title: "Aligment",
+ image: UIImage(systemName: "text.alignright"),
+ children: configureAligmentActions(for: maximumEndpointPreference))
+
+ let minimumEndpointMenu = UIMenu(title: "Minimum", children: [minimumTextColorAction, minimumAligmentMenu])
+ let maximumEndpointMenu = UIMenu(title: "Maximum", children: [maximumTextColorAction, maximumAligmentMenu])
+
+ return UIMenu(title: "Endpoint",
+ image: UIImage(systemName: "filemenu.and.selection"),
+ children: [minimumEndpointMenu, maximumEndpointMenu])
+ }
+
+ private func configureAligmentActions(for endpoint: EndpointPreference) -> [UIAction] {
+ var aligmentActions = [UIAction]()
+ let aligments = CATextLayerAlignmentMode.allCases
+ aligments.forEach { aligment in
+ let action = UIAction(title: aligment.rawValue,
+ state: aligment.rawValue == endpoint.aligmentMode.rawValue ? .on : .off) { [weak self] _ in
+ endpoint.aligmentMode = aligment
+ self?.configureMinimumEndpoint()
+ }
+ aligmentActions.append(action)
+ }
+
+ return aligmentActions
+ }
+
+ private func presentColorPicker() {
+ let colorPicker = UIColorPickerViewController()
+ colorPicker.delegate = self
+ present(colorPicker, animated: true)
+ }
+
+ // MARK: - Actions
+
+ @IBAction private func didChangeSegment(_ sender: UISegmentedControl) {
+ switch sender.selectedSegmentIndex {
+ case 1:
+ slider.direction = .rightToLeft
+ default:
+ slider.direction = .leftToRight
+ }
+ }
+
+}
+
+extension CATextLayerAlignmentMode: CaseIterable {
+ public static var allCases: [CATextLayerAlignmentMode] {
+ [.left, .natural, .center, .right, .justified]
+ }
}
diff --git a/Slider_Example/Flows/Main/Extensions/CodeViewController+SliderDelegate.swift b/Slider_Example/Flows/Main/Extensions/CodeViewController+SliderDelegate.swift
index bc26707..f277b77 100644
--- a/Slider_Example/Flows/Main/Extensions/CodeViewController+SliderDelegate.swift
+++ b/Slider_Example/Flows/Main/Extensions/CodeViewController+SliderDelegate.swift
@@ -1,9 +1,25 @@
//
// CodeViewController+SliderDelegate.swift
-// Slider_Example
//
-// Created by Рамиз Кичибеков on 31.03.2020.
-// Copyright © 2020 Ramiz Kichibekov. All rights reserved.
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
//
import UIKit
diff --git a/Slider_Example/Flows/Main/Extensions/CodeViewController+UIColorPickerViewControllerDelegate.swift b/Slider_Example/Flows/Main/Extensions/CodeViewController+UIColorPickerViewControllerDelegate.swift
new file mode 100644
index 0000000..22c59c0
--- /dev/null
+++ b/Slider_Example/Flows/Main/Extensions/CodeViewController+UIColorPickerViewControllerDelegate.swift
@@ -0,0 +1,60 @@
+//
+// CodeViewController+UIColorPickerViewControllerDelegate.swift
+//
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+import UIKit
+
+extension CodeViewController: UIColorPickerViewControllerDelegate {
+
+ func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
+ let selectedColor = viewController.selectedColor
+ switch selectedColorPickerType {
+ case .endpoint(let endpoint):
+ if endpoint == .minimum {
+ preference.minimumEndpointPreference.foregroundColor = selectedColor.cgColor
+ configureMinimumEndpoint()
+ } else {
+ preference.maximumEndpointPreference.foregroundColor = selectedColor.cgColor
+ configureMaximumEndpoint()
+ }
+ case .thumb:
+ preference.thumbPreference.backgroundColor = selectedColor
+ configureThumb()
+ case .track(let track):
+ if track == .max {
+ preference.trackPreference.maxColor = selectedColor
+ } else if track == .min {
+ preference.trackPreference.minColor = selectedColor
+ } else {
+ preference.trackPreference.reverseMinColor = selectedColor
+ }
+ configureTrack()
+ default: break
+ }
+ }
+
+ func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
+ viewController.dismiss(animated: true)
+ }
+
+}
diff --git a/Slider_Example/Flows/Main/View/Base.lproj/Main.storyboard b/Slider_Example/Flows/Main/View/Base.lproj/Main.storyboard
index 93969e6..cc43c8c 100644
--- a/Slider_Example/Flows/Main/View/Base.lproj/Main.storyboard
+++ b/Slider_Example/Flows/Main/View/Base.lproj/Main.storyboard
@@ -5,55 +5,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -61,85 +15,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -149,17 +30,16 @@
+
-
-
+
-
-
+
@@ -172,7 +52,7 @@
-
+
@@ -181,8 +61,6 @@
-
-
-
+
diff --git a/Slider_Example/Manager/PreferenceManager.swift b/Slider_Example/Manager/PreferenceManager.swift
new file mode 100644
index 0000000..e502af3
--- /dev/null
+++ b/Slider_Example/Manager/PreferenceManager.swift
@@ -0,0 +1,119 @@
+//
+// PreferenceManager.swift
+//
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+import UIKit
+import QuartzCore
+
+final class PreferenceManager {
+
+ let thumbPreference: ThumbPreference
+ let trackPreference: TrackPreference
+ let minimumEndpointPreference: EndpointPreference
+ let maximumEndpointPreference: EndpointPreference
+ let hapticPreference: HapticPreference
+
+ init(thumbPreference: ThumbPreference = ThumbPreference(),
+ trackPreference: TrackPreference = TrackPreference(),
+ minimumEndpointPreference: EndpointPreference = EndpointPreference(),
+ maximumEndpointPreference: EndpointPreference = EndpointPreference(),
+ hapticPreference: HapticPreference = HapticPreference()) {
+ self.thumbPreference = thumbPreference
+ self.trackPreference = trackPreference
+ self.minimumEndpointPreference = minimumEndpointPreference
+ self.maximumEndpointPreference = maximumEndpointPreference
+ self.hapticPreference = hapticPreference
+ }
+
+ class func reset() -> PreferenceManager {
+ .init()
+ }
+
+}
+
+final class HapticPreference {
+
+ var reachMinMaxValues = true
+ var valueChanges = true
+ var directionChanges = true
+
+ init(reachMinMaxValues: Bool = true,
+ valueChanges: Bool = true,
+ directionChanges: Bool = true) {
+ self.reachMinMaxValues = reachMinMaxValues
+ self.valueChanges = valueChanges
+ self.directionChanges = directionChanges
+ }
+
+}
+
+final class EndpointPreference {
+ var foregroundColor: CGColor
+ var aligmentMode: CATextLayerAlignmentMode
+
+ init(foregroundColor: CGColor = CGColor(red: 1, green: 1, blue: 1, alpha: 1),
+ aligmentMode: CATextLayerAlignmentMode = .center) {
+ self.foregroundColor = foregroundColor
+ self.aligmentMode = aligmentMode
+ }
+}
+
+final class TrackPreference {
+ var maxColor: UIColor
+ var minColor: UIColor
+ var reverseMinColor: UIColor
+
+ init(maxColor: UIColor = UIColor(red: 191 / 255,
+ green: 194 / 255,
+ blue: 209 / 255,
+ alpha: 1),
+ minColor: UIColor = UIColor(red: .zero,
+ green: 122 / 255,
+ blue: 1,
+ alpha: 1),
+ reverseMinColor: UIColor = UIColor(red: 247 / 255,
+ green: 73 / 255,
+ blue: 2 / 255,
+ alpha: 1)) {
+ self.maxColor = maxColor
+ self.minColor = minColor
+ self.reverseMinColor = reverseMinColor
+ }
+}
+
+final class ThumbPreference {
+ var backgroundColor: UIColor = .white
+ var textColor: UIColor = UIColor(red: .zero,
+ green: 74 / 255,
+ blue: 150 / 255,
+ alpha: 1)
+
+ init(backgroundColor: UIColor = .white,
+ textColor: UIColor = UIColor(red: .zero,
+ green: 74 / 255,
+ blue: 150 / 255,
+ alpha: 1)) {
+ self.backgroundColor = backgroundColor
+ self.textColor = textColor
+ }
+}
diff --git a/Sources/Configurations/RangeEndpointsConfiguration.swift b/Sources/Configurations/RangeEndpointsConfiguration.swift
new file mode 100644
index 0000000..a382e3d
--- /dev/null
+++ b/Sources/Configurations/RangeEndpointsConfiguration.swift
@@ -0,0 +1,44 @@
+//
+// ThumbConfiguration.swift
+//
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+import Foundation
+import QuartzCore
+
+public struct RangeEndpointsConfiguration {
+
+ let anchorPoint: CGPoint
+ let foregroundColor: CGColor
+ let fontSize: CGFloat
+ let aligmentMode: CATextLayerAlignmentMode
+
+ public init(anchorPoint: CGPoint = CGPoint(x: 0.5, y: 0.5),
+ foregroundColor: CGColor = CGColor(red: 1, green: 1, blue: 1, alpha: 1),
+ fontSize: CGFloat = 12,
+ aligmentMode: CATextLayerAlignmentMode = .center) {
+ self.anchorPoint = anchorPoint
+ self.foregroundColor = foregroundColor
+ self.fontSize = fontSize
+ self.aligmentMode = aligmentMode
+ }
+}
diff --git a/Sources/Configurations/ThumbConfiguration.swift b/Sources/Configurations/ThumbConfiguration.swift
new file mode 100644
index 0000000..052a0df
--- /dev/null
+++ b/Sources/Configurations/ThumbConfiguration.swift
@@ -0,0 +1,41 @@
+//
+// ThumbConfiguration.swift
+//
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+import UIKit
+
+public struct ThumbConfiguration {
+
+ public var backgroundColor: UIColor
+ public var fontSize: CGFloat
+ public var size: CGSize
+
+ public init(backgroundColor: UIColor = .white,
+ fontSize: CGFloat = 14,
+ size: CGSize = CGSize(width: 60, height: 36)) {
+ self.backgroundColor = backgroundColor
+ self.fontSize = fontSize
+ self.size = size
+ }
+
+}
diff --git a/Sources/Configurations/TrackConfiguration.swift b/Sources/Configurations/TrackConfiguration.swift
new file mode 100644
index 0000000..8ecf6d4
--- /dev/null
+++ b/Sources/Configurations/TrackConfiguration.swift
@@ -0,0 +1,56 @@
+//
+// TrackConfiguration.swift
+//
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+import UIKit
+
+public struct TrackConfiguration {
+
+ public var maxColor: UIColor
+ public var minColor: UIColor
+ public var reverseMinColor: UIColor
+ public var height: CGFloat
+ public var inset: CGFloat
+
+ public init(maxColor: UIColor = UIColor(red: 191 / 255,
+ green: 194 / 255,
+ blue: 209 / 255,
+ alpha: 1),
+ minColor: UIColor = UIColor(red: .zero,
+ green: 122 / 255,
+ blue: 1,
+ alpha: 1),
+ reverseMinColor: UIColor = UIColor(red: 247 / 255,
+ green: 73 / 255,
+ blue: 2 / 255,
+ alpha: 1),
+ height: CGFloat = 36,
+ inset: CGFloat = .zero) {
+ self.maxColor = maxColor
+ self.minColor = minColor
+ self.reverseMinColor = reverseMinColor
+ self.height = height
+ self.inset = inset
+ }
+
+}
diff --git a/Sources/Enums/Animation.swift b/Sources/Enums/Animation.swift
new file mode 100644
index 0000000..82b3339
--- /dev/null
+++ b/Sources/Enums/Animation.swift
@@ -0,0 +1,38 @@
+//
+// Animation.swift
+//
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+import Foundation
+import QuartzCore
+
+public enum AnimationStyle {
+ case none
+ case `default`
+
+ var animationDuration: TimeInterval {
+ switch self {
+ case .none: .zero
+ case .default: CATransaction.animationDuration()
+ }
+ }
+}
diff --git a/Sources/Enums/DirectionEnum.swift b/Sources/Enums/Direction.swift
similarity index 79%
rename from Sources/Enums/DirectionEnum.swift
rename to Sources/Enums/Direction.swift
index 9b60637..863d90f 100644
--- a/Sources/Enums/DirectionEnum.swift
+++ b/Sources/Enums/Direction.swift
@@ -1,7 +1,7 @@
//
-// DirectionEnum.swift
+// Direction.swift
//
-// Copyright (c) 2020 Ramiz Kichibekov (https://github.com/ramiz69)
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -22,17 +22,8 @@
// THE SOFTWARE.
//
-import UIKit
+import Foundation
-public enum DirectionEnum {
-
+public enum Direction {
case leftToRight, rightToLeft
-
- public init(withValue value: Int) {
- switch value {
- case .zero: self = .leftToRight
- default: self = .rightToLeft
- }
- }
-
}
diff --git a/Sources/Extensions/Slider+UITouch.swift b/Sources/Extensions/Slider+UITouch.swift
index 2d046ed..a4913c1 100644
--- a/Sources/Extensions/Slider+UITouch.swift
+++ b/Sources/Extensions/Slider+UITouch.swift
@@ -29,6 +29,7 @@ extension Slider {
public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
previousTouchPoint = touch.location(in: self)
if thumbLayer.frame.contains(previousTouchPoint) {
+ didBeginTracking()
delegate?.didBeginTracking(self)
return true
@@ -39,10 +40,16 @@ extension Slider {
public override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let touchPoint = touch.location(in: self)
- let delta = (touchPoint.x - previousTouchPoint.x).rounded(.toNearestOrEven)
- let ratio = delta / usableTrackingLength
- let valueDelta = ((maximum - minimum) * ratio).rounded(.toNearestOrEven)
- let tempValue = value + valueDelta
+ let deltaLocation = (touchPoint.x - previousTouchPoint.x).rounded(.toNearestOrEven)
+ let ratio = deltaLocation / usableTrackingLength
+ let deltaValue = ((maximum - minimum) * ratio).rounded(.toNearestOrEven)
+ let tempValue: CGFloat
+ switch direction {
+ case .leftToRight:
+ tempValue = value + deltaValue
+ case .rightToLeft:
+ tempValue = value - deltaValue
+ }
let noOfStep = (tempValue / step).rounded(.toNearestOrEven)
var currentValue = noOfStep * step
if (currentValue == maximum || currentValue == minimum) && currentValue != value {
@@ -56,13 +63,11 @@ extension Slider {
if currentValue == value {
return true
}
+
value = currentValue
hapticConfiguration.valueGenerate()
previousTouchPoint = touchPoint
- delegate?.didContinueTracking(self)
- if continuous {
- sendActions(for: .valueChanged)
- }
+ sendActions(for: .valueChanged)
return true
}
@@ -70,6 +75,7 @@ extension Slider {
public override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
+ endTracking()
if step > .zero {
let noOfStep = (value / step).rounded(.toNearestOrEven)
value = noOfStep * step
diff --git a/Sources/Protocols/Protocols.swift b/Sources/Protocols/SliderDelegate.swift
similarity index 94%
rename from Sources/Protocols/Protocols.swift
rename to Sources/Protocols/SliderDelegate.swift
index bb93f71..1372bd2 100644
--- a/Sources/Protocols/Protocols.swift
+++ b/Sources/Protocols/SliderDelegate.swift
@@ -1,5 +1,5 @@
//
-// Protocols.swift
+// SliderDelegate.swift
//
// Copyright (c) 2020 Ramiz Kichibekov (https://github.com/ramiz69)
//
@@ -21,8 +21,11 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
-import UIKit
+import Foundation
+import CoreFoundation
+
+@MainActor
public protocol SliderDelegate: AnyObject {
func slider(_ slider: Slider, displayTextForValue value: CGFloat) -> String
func didBeginTracking(_ slider: Slider)
diff --git a/Sources/Slider.swift b/Sources/Slider.swift
index e982b51..71e1652 100644
--- a/Sources/Slider.swift
+++ b/Sources/Slider.swift
@@ -23,6 +23,7 @@
//
import UIKit
+import QuartzCore
open class Slider: UIControl {
@@ -31,23 +32,21 @@ open class Slider: UIControl {
if let maxText: String = delegate?.slider(self, displayTextForValue: maximum),
!maxText.isEmpty
{
- let newWidth = maxText.size(withConstrainedWidth: thumbHeight,
- font: UIFont.boldSystemFont(ofSize: fontSize)).width
- thumbWidth = CGFloat(max(newWidth, thumbWidth))
- thumbLayer.bounds = CGRect(origin: .zero,
- size: CGSize(width: thumbWidth,
- height: thumbHeight))
- thumbLayer.position = CGPoint(x: positionForValue(value: minimum),
- y: bounds.height / 2)
- thumbLayer.cornerRadius = thumbHeight / 2
+ let thumbSize = thumbConfiguration.size
+ let newWidth = maxText.size(withConstrainedWidth: thumbSize.height,
+ font: UIFont.boldSystemFont(ofSize: thumbConfiguration.fontSize)).width
+ thumbConfiguration.size.width = CGFloat(max(newWidth, thumbSize.width))
+ thumbLayer.bounds = CGRect(origin: .zero, size: thumbSize)
+ thumbLayer.position = positionForValue(value: minimum)
+ thumbLayer.cornerRadius = thumbSize.height / 2
thumbLayer.shadowPath = UIBezierPath(roundedRect: thumbLayer.bounds,
- cornerRadius: thumbHeight / 2).cgPath
+ cornerRadius: thumbLayer.cornerRadius).cgPath
}
updateThumbLayersText()
}
}
- // MARK: - Customization properties
+ // MARK: Customization properties
final public var value: CGFloat = 10 {
didSet {
@@ -58,7 +57,7 @@ open class Slider: UIControl {
value = minimum
}
reinitComponentValues()
- redrawLayers()
+ setNeedsLayersDisplay()
}
}
@@ -68,7 +67,7 @@ open class Slider: UIControl {
maximum = minimum
}
reinitComponentValues()
- redrawLayers()
+ setNeedsLayersDisplay()
}
}
@@ -81,7 +80,7 @@ open class Slider: UIControl {
value = maximum
}
reinitComponentValues()
- redrawLayers()
+ setNeedsLayersDisplay()
}
}
@@ -99,172 +98,117 @@ open class Slider: UIControl {
final public var cornerRadius: CGFloat = 16 {
didSet {
reinitComponentValues()
- redrawLayers()
- }
- }
-
- final public var thumbBackgroundColor: UIColor = UIColor.white {
- didSet {
- thumbLayer.backgroundColor = thumbBackgroundColor.cgColor
- redrawLayers()
- }
- }
-
- final public var thumbTextColor: UIColor = UIColor(red: .zero,
- green: 74 / 255,
- blue: 150 / 255,
- alpha: 1) {
- didSet {
- thumbLayer.foregroundColor = thumbTextColor.cgColor
- redrawLayers()
+ setNeedsLayersDisplay()
}
}
- final public var continuous: Bool = true
-
- final public var fontSize: CGFloat = 14 {
+ public var thumbConfiguration = ThumbConfiguration() {
didSet {
- thumbLayer.setNeedsDisplay()
+ updateThumbLayersText()
+ setNeedsLayersDisplay()
}
}
- final public var trackHeight: CGFloat = 36 {
+ public var trackConfiguration = TrackConfiguration() {
didSet {
- layoutSubviews()
+ reinitComponentValues()
+ setNeedsLayersDisplay()
}
}
- final public var trackInset: CGFloat = .zero {
+ public var maximumEndpointConfiguration = RangeEndpointsConfiguration() {
didSet {
- layoutSubviews()
+ initEndpointLayer(.maximum)
}
}
- final public var thumbHeight: CGFloat = 36 {
+ public var minimumEndpointConfiguration = RangeEndpointsConfiguration() {
didSet {
- initThumbLayer()
- layoutSubviews()
- redrawLayers()
+ initEndpointLayer(.minimum)
}
}
- final public var thumbWidth: CGFloat = 60 {
- didSet {
- initThumbLayer()
- layoutSubviews()
- redrawLayers()
- }
- }
-
- open var trackMaxColor: UIColor = UIColor(red: 191 / 255,
- green: 194 / 255,
- blue: 209 / 255,
- alpha: 1) {
- didSet {
- reinitComponentValues()
- redrawLayers()
- }
- }
+ public var hapticConfiguration: HapticConfiguration = .init(reachLimitValueHapticEnabled: true,
+ changeValueHapticEnabled: false,
+ changeDirectionHapticEnabled: true,
+ reachImpactGeneratorStyle: .medium,
+ changeValueImpactGeneratorStyle: .light)
- open var trackMinColor: UIColor = UIColor(red: .zero,
- green: 122 / 255,
- blue: 1,
- alpha: 1) {
- didSet {
- reinitComponentValues()
- redrawLayers()
- }
- }
+ // MARK: Properties
- open var reverseTrackMinColor: UIColor = UIColor(red: 247 / 255,
- green: 73 / 255,
- blue: 2 / 255,
- alpha: 1) {
- didSet {
- reinitComponentValues()
- redrawLayers()
- }
+ open override var intrinsicContentSize: CGSize {
+ CGSize(width: UIView.noIntrinsicMetric, height: trackConfiguration.height)
}
- // MARK: - Properties
-
- final public var direction: DirectionEnum = .leftToRight {
+ final public var direction: Direction = .leftToRight {
didSet {
hapticConfiguration.directionGenerate()
- CATransaction.begin()
- CATransaction.setDisableActions(true)
- CATransaction.setAnimationDuration(.zero)
- let affineTransform = CATransform3DMakeAffineTransform(direction == .leftToRight ? .identity : CGAffineTransform.identity.rotated(by: .pi))
- transform = direction == .leftToRight ? .identity : CGAffineTransform.identity.rotated(by: .pi)
- thumbLayer.transform = affineTransform
- minimumLayer.transform = affineTransform
- maximumLayer.transform = affineTransform
- CATransaction.commit()
- reinitComponentValues()
redrawLayers()
+ reinitComponentValues()
+ setNeedsLayersDisplay()
}
}
-
- public var hapticConfiguration: HapticConfiguration = .init(reachLimitValueHapticEnabled: true,
- changeValueHapticEnabled: false,
- changeDirectionHapticEnabled: true,
- reachImpactGeneratorStyle: .medium,
- changeValueImpactGeneratorStyle: .light)
- public let trackLayer = SliderTrackLayer()
- public let thumbLayer = SliderTextLayer()
- public let minimumLayer = SliderMinimumTextLayer()
- public let maximumLayer = SliderMaximumTextLayer()
final public var previousTouchPoint: CGPoint = .zero
final public var usableTrackingLength: CGFloat = .zero
+ final public var animationStyle: AnimationStyle = .none
+ final public var continuous: Bool = true
- open override var intrinsicContentSize: CGSize {
- CGSize(width: UIView.noIntrinsicMetric, height: trackHeight)
- }
+ let thumbLayer = ThumbLayer()
+ private let trackLayer = SliderTrackLayer()
+ private let minimumLayer = TextLayer()
+ private let maximumLayer = TextLayer()
+
+ private var displayLink: CADisplayLink?
+ private var isDirectionChangeAnimationInProgress = false
- private let trackMaskLayer = CALayer()
+ private enum Endpoint: CaseIterable {
+ case minimum, maximum
+ }
- // MARK: - Initial methods
+ // MARK: Initial methods
- required public override init(frame: CGRect) {
+ public override init(frame: CGRect) {
super.init(frame: frame)
initialControl()
initLayers()
- commonInit()
}
@available(*, unavailable)
- required public init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
+ required public init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
}
- // MARK: - Life cycle
+ // MARK: Life cycle
public override func layoutSubviews() {
super.layoutSubviews()
- trackLayer.frame = trackRectForBound(bounds)
- trackMaskLayer.masksToBounds = true
- trackMaskLayer.frame = trackRectForBound(bounds)
- trackMaskLayer.cornerRadius = cornerRadius
- invalidateIntrinsicContentSize()
- commonInit()
- updateThumbLayersPosition()
- redrawLayers()
+ if !isDirectionChangeAnimationInProgress {
+ usableTrackingLength = bounds.width - thumbConfiguration.size.width
+ trackLayer.frame = trackRectForBounds()
+ minimumLayer.position = positionForValue(value: minimum)
+ maximumLayer.position = positionForValue(value: maximum)
+ updateSlider()
+ }
}
- public override func prepareForInterfaceBuilder() {
- trackLayer.frame = trackRectForBound(bounds)
- commonInit()
- updateThumbLayersPosition()
- redrawLayers()
+ // MARK: Public methods
+
+ func didBeginTracking() {
+ displayLink = CADisplayLink(target: self, selector: #selector(updateSlider))
+ displayLink?.add(to: .current, forMode: .common)
}
- // MARK: - Private methods
+ func endTracking() {
+ displayLink?.invalidate()
+ displayLink = nil
+ }
+
+ // MARK: Private methods
private func initialControl() {
backgroundColor = .clear
- translatesAutoresizingMaskIntoConstraints = false
layer.addSublayer(trackLayer)
layer.addSublayer(minimumLayer)
layer.addSublayer(maximumLayer)
@@ -272,137 +216,165 @@ open class Slider: UIControl {
}
private func initLayers() {
- trackLayer.contentsScale = UIScreen.main.scale
- trackLayer.frame = trackRectForBound(bounds)
- trackLayer.setNeedsDisplay()
- trackMaskLayer.masksToBounds = true
- trackMaskLayer.frame = trackRectForBound(bounds)
- trackMaskLayer.cornerRadius = cornerRadius
- layer.insertSublayer(trackMaskLayer, at: .zero)
+ trackLayer.contentsScale = getScallingFactor()
+ trackLayer.frame = trackRectForBounds()
reinitComponentValues()
initThumbLayer()
- initMinimumLayer()
- initMaximumLayer()
+ let endpoints = Endpoint.allCases
+ endpoints.forEach { initEndpointLayer($0) }
updateThumbLayersText()
}
private func initThumbLayer() {
thumbLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
- thumbLayer.bounds = CGRect(x: .zero, y: .zero, width: thumbWidth, height: thumbHeight)
- let xPosition = positionForValue(value: minimum)
- thumbLayer.position = CGPoint(x: xPosition, y: bounds.height / 2)
- thumbLayer.foregroundColor = trackMinColor.cgColor
- thumbLayer.cornerRadius = thumbHeight / 2
- thumbLayer.font = UIFont.systemFont(ofSize: fontSize, weight: .black)
- thumbLayer.fontSize = fontSize
- thumbLayer.backgroundColor = thumbBackgroundColor.cgColor
+ thumbLayer.bounds = CGRect(origin: .zero, size: thumbConfiguration.size)
+ thumbLayer.position = positionForValue(value: minimum)
+ thumbLayer.foregroundColor = trackConfiguration.minColor.cgColor
+ thumbLayer.cornerRadius = thumbConfiguration.size.height / 2
+ thumbLayer.font = UIFont.systemFont(ofSize: thumbConfiguration.fontSize, weight: .black)
+ thumbLayer.fontSize = thumbConfiguration.fontSize
thumbLayer.alignmentMode = .center
- thumbLayer.contentsScale = UIScreen.main.scale
+ thumbLayer.contentsScale = getScallingFactor()
thumbLayer.masksToBounds = false
- thumbLayer.shadowOffset = CGSize(width: 0, height: 0.5)
+ thumbLayer.shadowOffset = CGSize(width: .zero, height: 0.5)
thumbLayer.shadowColor = UIColor.black.cgColor
thumbLayer.shadowRadius = 2
thumbLayer.shadowOpacity = 0.125
thumbLayer.shadowPath = UIBezierPath(roundedRect: thumbLayer.bounds,
- cornerRadius: thumbHeight / 2).cgPath
- }
-
- private func initMinimumLayer() {
- minimumLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
- minimumLayer.bounds = CGRect(x: .zero, y: .zero, width: thumbWidth, height: thumbHeight)
- let xPosition = positionForValue(value: minimum)
- minimumLayer.position = CGPoint(x: xPosition, y: bounds.height / 2)
- minimumLayer.foregroundColor = UIColor.white.cgColor
- minimumLayer.fontSize = 12
- minimumLayer.alignmentMode = .center
- minimumLayer.contentsScale = UIScreen.main.scale
- }
-
- private func initMaximumLayer() {
- maximumLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
- maximumLayer.bounds = CGRect(x: .zero, y: .zero, width: thumbWidth, height: thumbHeight)
- let xPosition = positionForValue(value: maximum)
- maximumLayer.position = CGPoint(x: xPosition, y: bounds.height / 2)
- maximumLayer.foregroundColor = UIColor.white.cgColor
- maximumLayer.fontSize = 12
- maximumLayer.alignmentMode = .center
- maximumLayer.contentsScale = UIScreen.main.scale
+ cornerRadius: thumbLayer.cornerRadius).cgPath
+ }
+
+ private func initEndpointLayer(_ endpoint: Endpoint) {
+ let layerFrame = CGRect(origin: .zero, size: thumbConfiguration.size)
+ let scale = getScallingFactor()
+ switch endpoint {
+ case .minimum:
+ minimumLayer.anchorPoint = minimumEndpointConfiguration.anchorPoint
+ minimumLayer.bounds = layerFrame
+ minimumLayer.position = positionForValue(value: minimum)
+ minimumLayer.foregroundColor = minimumEndpointConfiguration.foregroundColor
+ minimumLayer.fontSize = minimumEndpointConfiguration.fontSize
+ minimumLayer.alignmentMode = minimumEndpointConfiguration.aligmentMode
+ minimumLayer.contentsScale = scale
+ case .maximum:
+ maximumLayer.anchorPoint = maximumEndpointConfiguration.anchorPoint
+ maximumLayer.bounds = layerFrame
+ maximumLayer.position = positionForValue(value: maximum)
+ maximumLayer.foregroundColor = maximumEndpointConfiguration.foregroundColor
+ maximumLayer.fontSize = maximumEndpointConfiguration.fontSize
+ maximumLayer.alignmentMode = maximumEndpointConfiguration.aligmentMode
+ maximumLayer.contentsScale = scale
+ }
}
- private func commonInit() {
- usableTrackingLength = bounds.width - thumbWidth
- translatesAutoresizingMaskIntoConstraints = false
+ private func getScallingFactor() -> CGFloat {
+ window?.screen.scale ?? UIScreen.main.scale
}
// MARK: Update methods
private func reinitComponentValues() {
- trackLayer.minimumValue = minimum
- trackLayer.maximumValue = maximum
- trackLayer.trackMaxColor = trackMaxColor
- trackLayer.trackMinColor = direction == .leftToRight ? trackMinColor : reverseTrackMinColor
- trackLayer.thumbWidth = thumbWidth
- trackLayer.value = value
+ trackLayer.trackBackgroundColor = trackConfiguration.maxColor.cgColor
+ trackLayer.fillColor = direction == .leftToRight ? trackConfiguration.minColor.cgColor : trackConfiguration.reverseMinColor.cgColor
trackLayer.cornerRadius = cornerRadius
-
updateThumbLayersText()
- updateThumbLayersPosition()
}
private func redrawLayers() {
+ isDirectionChangeAnimationInProgress = true
+ animateThumbLayer {
+ self.isDirectionChangeAnimationInProgress = false
+ }
+ trackLayer.displayIfNeeded()
+
+ CATransaction.begin()
+ CATransaction.setAnimationDuration(.zero)
+ minimumLayer.position = positionForValue(value: minimum)
+ maximumLayer.position = positionForValue(value: maximum)
+ CATransaction.commit()
+ }
+
+ private func animateThumbLayer(_ completionBlock: (() -> Void)? = nil) {
+ let thumbSize = thumbConfiguration.size
+ CATransaction.begin()
+ CATransaction.setCompletionBlock(completionBlock)
+ CATransaction.setAnimationDuration(animationStyle.animationDuration)
+ CATransaction.setAnimationTimingFunction(CATransaction.animationTimingFunction())
+ thumbLayer.position = positionForValue(value: value)
+ trackLayer.updateFillLayerForAnimation(CGRect(origin: CGPoint(x: thumbLayer.position.x, y: .zero),
+ size: CGSize(width: thumbSize.width / 2, height: thumbSize.height)))
+ updateSlider()
+ CATransaction.commit()
+ }
+
+ private func setNeedsLayersDisplay() {
trackLayer.setNeedsDisplay()
thumbLayer.setNeedsDisplay()
- trackMaskLayer.setNeedsDisplay()
}
private func updateThumbLayersText() {
- thumbLayer.trackMinColor = direction == .leftToRight ? trackMinColor : reverseTrackMinColor
- thumbLayer.trackMaxColor = trackMaxColor
+ thumbLayer.foregroundColor = direction == .leftToRight ? trackConfiguration.minColor.cgColor : trackConfiguration.reverseMinColor.cgColor
+ thumbLayer.backgroundColor = thumbConfiguration.backgroundColor.cgColor
thumbLayer.string = textForValue(value)
minimumLayer.string = textForValue(minimum)
maximumLayer.string = textForValue(maximum)
}
- private func updateThumbLayersPosition() {
+ @objc
+ private func updateSlider() {
+ let valueRatio = (value - minimum) / (maximum - minimum)
+ let thumbSize = thumbConfiguration.size
+ let thumbPositionX = valueRatio * (bounds.width - thumbSize.width) + (value == minimum ? thumbSize.width : thumbSize.width / 2)
+ let fillFrame: CGRect
+ switch direction {
+ case .leftToRight:
+ fillFrame = CGRect(x: .zero,
+ y: .zero,
+ width: thumbPositionX,
+ height: bounds.height)
+ case .rightToLeft:
+ fillFrame = CGRect(x: bounds.width - thumbPositionX,
+ y: .zero,
+ width: thumbPositionX,
+ height: bounds.height)
+
+ }
CATransaction.begin()
CATransaction.setDisableActions(true)
CATransaction.setAnimationDuration(.zero)
-
- let thumbCenterX = positionForValue(value: value)
- thumbLayer.position = CGPoint(x: thumbCenterX,
- y: bounds.height / 2)
- let maximumXPosition = positionForValue(value: maximum)
- maximumLayer.position = CGPoint(x: maximumXPosition,
- y: bounds.height / 2)
- let minimumXPosition = positionForValue(value: minimum)
- minimumLayer.position = CGPoint(x: minimumXPosition,
- y: bounds.height / 2)
+ thumbLayer.position = positionForValue(value: value)
+ trackLayer.fillFrame = fillFrame
CATransaction.commit()
+ setNeedsLayersDisplay()
}
- private func updateLayersValue() {
- updateThumbLayersText()
- trackLayer.value = value
- }
-
- private func positionForValue(value: CGFloat) -> CGFloat {
+ private func positionForValue(value: CGFloat) -> CGPoint {
+ let thumbWidth = thumbConfiguration.size.width
+ let yPosition = bounds.height / 2
if minimum == maximum {
- return thumbWidth / 2
+ return CGPoint(x: thumbWidth / 2, y: yPosition)
+ }
+ let xPosition: CGFloat
+ switch direction {
+ case .leftToRight:
+ xPosition = usableTrackingLength * (value - minimum) / (maximum - minimum) + thumbWidth / 2
+ case .rightToLeft:
+ xPosition = bounds.width - (usableTrackingLength * (value - minimum) / (maximum - minimum) + thumbWidth / 2)
}
- return usableTrackingLength * (value - minimum) / (maximum - minimum) + thumbWidth / 2
+ return CGPoint(x: xPosition, y: yPosition)
}
- private func trackRectForBound(_ bound: CGRect) -> CGRect {
- return CGRect(x: trackInset,
- y: (bound.height - trackHeight) / 2,
- width: bound.width - 2 * trackInset,
- height: trackHeight)
+ private func trackRectForBounds() -> CGRect {
+ CGRect(x: trackConfiguration.inset,
+ y: (bounds.height - trackConfiguration.height) / 2,
+ width: bounds.width - 2 * trackConfiguration.inset,
+ height: trackConfiguration.height)
}
private func textForValue(_ value: CGFloat) -> String {
- guard let delegate = delegate else {
+ guard let delegate else {
return String(format: "%.0f", value)
}
diff --git a/Sources/SliderTrackLayer.swift b/Sources/SliderTrackLayer.swift
index f47a510..a7cb9fe 100644
--- a/Sources/SliderTrackLayer.swift
+++ b/Sources/SliderTrackLayer.swift
@@ -22,88 +22,73 @@
// THE SOFTWARE.
//
-import UIKit
+import Foundation
+import QuartzCore
+import CoreGraphics
-public final class SliderTrackLayer: CALayer {
+final class SliderTrackLayer: CALayer {
- // MARK: - Properties
+ // MARK: Properties
- public var value: CGFloat = .zero
- public var minimumValue: CGFloat = .zero
- public var maximumValue: CGFloat = 1
- public var thumbWidth: CGFloat = .zero
- public var trackMaxColor: UIColor!
- public var trackMinColor: UIColor!
-
- // MARK: - Initial methods
-
- public init(value: CGFloat = .zero,
- minimumValue: CGFloat = .zero,
- maximumValue: CGFloat = 1,
- thumbWidth: CGFloat = .zero,
- trackMaxColor: UIColor,
- trackMinColor: UIColor) {
- self.value = value
- self.minimumValue = minimumValue
- self.maximumValue = maximumValue
- self.thumbWidth = thumbWidth
- self.trackMaxColor = trackMaxColor
- self.trackMinColor = trackMinColor
- super.init()
+ var trackBackgroundColor: CGColor = CGColor(gray: .zero, alpha: 1) {
+ didSet {
+ backgroundLayer.backgroundColor = trackBackgroundColor
+ }
+ }
+ var fillColor: CGColor! {
+ didSet {
+ fillLayer.backgroundColor = fillColor
+ }
+ }
+ var fillFrame: CGRect = .zero {
+ didSet {
+ fillLayer.frame = fillFrame
+ }
}
+ private let backgroundLayer = CALayer()
+ private let fillLayer = CALayer()
- public override init(layer: Any) {
+ // MARK: Initial methods
+
+ override init(layer: Any) {
super.init(layer: layer)
+
+ configureLayer()
}
- public override init() {
+ override init() {
super.init()
+
+ configureLayer()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
- super.init(coder: coder)
+ fatalError("init(coder:) has not been implemented")
}
- // MARK: - Life cycle
+ // MARK: Life cycle
- override public func draw(in ctx: CGContext) {
- assert(trackMaxColor != nil, "trackMaxColor should not be nil. Check the color initialization.")
- assert(trackMinColor != nil, "trackMinColor should not be nil. Check the color initialization.")
- ctx.setFillColor(trackMaxColor.cgColor)
- ctx.addPath(UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath)
- ctx.fillPath()
+ override func layoutSublayers() {
+ super.layoutSublayers()
- let trackWidth = bounds.width - cornerRadius
- let range = maximumValue - minimumValue
- var thresholdX: CGFloat = ((value - minimumValue) / range * trackWidth)
- let averangeValue = value - minimumValue
- if averangeValue > .zero {
- if averangeValue >= maximumValue / 2 {
- thresholdX += thumbWidth / 6
- } else {
- thresholdX += thumbWidth / 3
- }
- }
- if value == maximumValue {
- thresholdX += -thumbWidth / 3
- }
- let width = thresholdX.rounded(.down)
- let trackMinSize = CGSize(width: width, height: bounds.height)
- let trackMinRect = CGRect(origin: .zero, size: trackMinSize)
- let trackMinPath = UIBezierPath(roundedRect: trackMinRect, cornerRadius: cornerRadius)
- ctx.setFillColor(trackMinColor.cgColor)
- ctx.addPath(trackMinPath.cgPath)
- ctx.fillPath()
-
- let widthDifference = value >= minimumValue ? .zero : bounds.width - thresholdX
- let trackMaxRect = CGRect(x: thresholdX - cornerRadius, y: .zero,
- width: widthDifference,
- height: bounds.height)
- let trackMaxPath = UIBezierPath(roundedRect: trackMaxRect,
- cornerRadius: cornerRadius)
- ctx.setFillColor(trackMinColor.cgColor)
- ctx.addPath(trackMaxPath.cgPath)
- ctx.fillPath()
+ backgroundLayer.frame = bounds
+ backgroundLayer.cornerRadius = cornerRadius
+ fillLayer.cornerRadius = cornerRadius
+ }
+
+ // MARK: Public methods
+
+ func updateFillLayerForAnimation(_ frame: CGRect) {
+ fillFrame = frame
+ }
+
+ // MARK: Private methods
+
+ private func configureLayer() {
+ backgroundLayer.masksToBounds = true
+ backgroundLayer.backgroundColor = trackBackgroundColor
+ addSublayer(backgroundLayer)
+ backgroundLayer.addSublayer(fillLayer)
}
}
diff --git a/Sources/TextLayer.swift b/Sources/TextLayer.swift
new file mode 100644
index 0000000..576de28
--- /dev/null
+++ b/Sources/TextLayer.swift
@@ -0,0 +1,41 @@
+//
+// TextLayer.swift
+//
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+import QuartzCore
+import CoreGraphics
+
+final class TextLayer: CATextLayer {
+
+ override func draw(in ctx: CGContext) {
+ let height = bounds.size.height
+ let yDiff = (height - fontSize) / 2 - fontSize / 10
+
+ ctx.saveGState()
+ ctx.translateBy(x: .zero, y: yDiff)
+ super.draw(in: ctx)
+
+ ctx.restoreGState()
+ }
+
+}
diff --git a/Sources/SliderTextLayer.swift b/Sources/ThumbLayer.swift
similarity index 51%
rename from Sources/SliderTextLayer.swift
rename to Sources/ThumbLayer.swift
index 00322b9..af8404a 100644
--- a/Sources/SliderTextLayer.swift
+++ b/Sources/ThumbLayer.swift
@@ -1,7 +1,7 @@
//
-// SliderTextLayer.swift
+// ThumbLayer.swift
//
-// Copyright (c) 2020 Ramiz Kichibekov (https://github.com/ramiz69)
+// Copyright (c) 2024 Ramiz Kichibekov (https://github.com/ramiz69)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -22,45 +22,21 @@
// THE SOFTWARE.
//
-import UIKit
+import Foundation
+import QuartzCore
+import CoreGraphics
-public class SliderTextLayer: CATextLayer {
+final class ThumbLayer: CATextLayer {
- // MARK: - Properties
+ // MARK: Life cycle
- public var trackMaxColor: UIColor!
- public var trackMinColor: UIColor!
-
- // MARK: - Initial methods
-
- public init(trackMaxColor: UIColor, trackMinColor: UIColor) {
- self.trackMaxColor = trackMaxColor
- self.trackMinColor = trackMinColor
- super.init()
- }
-
- public override init(layer: Any) {
- super.init(layer: layer)
- }
-
- public override init() {
- super.init()
- }
-
- @available(*, unavailable)
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- }
-
- // MARK: - Life cycle
-
- public override func setNeedsDisplay() {
+ override func setNeedsDisplay() {
super.setNeedsDisplay()
configureBorder()
}
- override public func draw(in ctx: CGContext) {
+ override func draw(in ctx: CGContext) {
let height = bounds.size.height
let yDiff = (height - fontSize) / 2 - fontSize / 10
@@ -71,28 +47,9 @@ public class SliderTextLayer: CATextLayer {
ctx.restoreGState()
}
- open func configureBorder() {
- assert(trackMaxColor != nil, "trackMaxColor should not be nil. Check the color initialization.")
- assert(trackMinColor != nil, "trackMinColor should not be nil. Check the color initialization.")
- foregroundColor = trackMinColor.cgColor
+ private func configureBorder() {
borderWidth = 4
- borderColor = trackMinColor.cgColor
- }
-
-}
-
-public final class SliderMinimumTextLayer: SliderTextLayer {
-
- public override func configureBorder() {
-
- }
-
-}
-
-public final class SliderMaximumTextLayer: SliderTextLayer {
-
- public override func configureBorder() {
-
+ borderColor = foregroundColor
}
}
diff --git a/example.gif b/example.gif
deleted file mode 100644
index effbe75..0000000
Binary files a/example.gif and /dev/null differ