From a5a15da7cdc714090250e3f8f521b1a37b5ec2b8 Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Wed, 2 Jan 2019 15:02:24 +0300 Subject: [PATCH 01/13] added few new utils --- Utils/Utils.xcodeproj/project.pbxproj | 60 +++++++++++++++ Utils/Utils/BlurBuilder/BlurBuilder.swift | 26 +++++++ Utils/Utils/RouteMeasurer/RouteMeasurer.swift | 73 +++++++++++++++++++ .../Utils/SettingsRouter/SettingsRouter.swift | 46 ++++++++++++ ...er+AdvancedNavigationStackManagement.swift | 41 +++++++++++ .../WordDeclinationConstructor.swift | 53 ++++++++++++++ 6 files changed, 299 insertions(+) create mode 100644 Utils/Utils/BlurBuilder/BlurBuilder.swift create mode 100644 Utils/Utils/RouteMeasurer/RouteMeasurer.swift create mode 100644 Utils/Utils/SettingsRouter/SettingsRouter.swift create mode 100644 Utils/Utils/UINavigationController/UINavigationController+AdvancedNavigationStackManagement.swift create mode 100644 Utils/Utils/WordDeclinationConstructor/WordDeclinationConstructor.swift diff --git a/Utils/Utils.xcodeproj/project.pbxproj b/Utils/Utils.xcodeproj/project.pbxproj index e389bcf..96d6701 100644 --- a/Utils/Utils.xcodeproj/project.pbxproj +++ b/Utils/Utils.xcodeproj/project.pbxproj @@ -13,6 +13,11 @@ 4F3ED9F0211C27CF0030DD45 /* Utils.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F3ED9E2211C27CF0030DD45 /* Utils.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4F3ED9FC211C27FD0030DD45 /* String+Attributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3ED9FB211C27FD0030DD45 /* String+Attributes.swift */; }; 80437D26214045EF0095A8D0 /* JailbreakDetect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80437D25214045EF0095A8D0 /* JailbreakDetect.swift */; }; + 907F0FE321DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE221DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift */; }; + 907F0FE621DCD3A0001CCB07 /* SettingsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE521DCD3A0001CCB07 /* SettingsRouter.swift */; }; + 907F0FE921DCD5C5001CCB07 /* BlurBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */; }; + 907F0FEC21DCD7E1001CCB07 /* RouteMeasurer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FEB21DCD7E1001CCB07 /* RouteMeasurer.swift */; }; + 907F0FEF21DCDB43001CCB07 /* WordDeclinationConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FEE21DCDB43001CCB07 /* WordDeclinationConstructor.swift */; }; E9B0630A2146924A0080C391 /* VibrationFeedbackManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B063092146924A0080C391 /* VibrationFeedbackManager.swift */; }; E9B0630C214692C30080C391 /* UIDevice+hasTapticEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B0630B214692C30080C391 /* UIDevice+hasTapticEngine.swift */; }; E9B0630E214693160080C391 /* UIDevice+hasHapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B0630D214693160080C391 /* UIDevice+hasHapticFeedback.swift */; }; @@ -39,6 +44,11 @@ 4F3ED9EF211C27CF0030DD45 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4F3ED9FB211C27FD0030DD45 /* String+Attributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Attributes.swift"; sourceTree = ""; }; 80437D25214045EF0095A8D0 /* JailbreakDetect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JailbreakDetect.swift; sourceTree = ""; }; + 907F0FE221DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+AdvancedNavigationStackManagement.swift"; sourceTree = ""; }; + 907F0FE521DCD3A0001CCB07 /* SettingsRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRouter.swift; sourceTree = ""; }; + 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurBuilder.swift; sourceTree = ""; }; + 907F0FEB21DCD7E1001CCB07 /* RouteMeasurer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteMeasurer.swift; sourceTree = ""; }; + 907F0FEE21DCDB43001CCB07 /* WordDeclinationConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordDeclinationConstructor.swift; sourceTree = ""; }; E9B063092146924A0080C391 /* VibrationFeedbackManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationFeedbackManager.swift; sourceTree = ""; }; E9B0630B214692C30080C391 /* UIDevice+hasTapticEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+hasTapticEngine.swift"; sourceTree = ""; }; E9B0630D214693160080C391 /* UIDevice+hasHapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+hasHapticFeedback.swift"; sourceTree = ""; }; @@ -93,10 +103,15 @@ 4F3ED9E1211C27CF0030DD45 /* Utils */ = { isa = PBXGroup; children = ( + 907F0FE721DCD5B9001CCB07 /* BlurBuilder */, 18F2361221D214CC00169AC9 /* Dictionary */, 80437D24214045B30095A8D0 /* JailbreakDetect */, + 907F0FEA21DCD7C6001CCB07 /* RouteMeasurer */, + 907F0FE421DCD375001CCB07 /* SettingsRouter */, 4F3ED9FA211C27E80030DD45 /* String */, + 907F0FE121DCD1DB001CCB07 /* UINavigationController */, E9B06306214691F30080C391 /* VibrationFeedbackManager */, + 907F0FED21DCDB1F001CCB07 /* WordDeclinationConstructor */, 4F3ED9E2211C27CF0030DD45 /* Utils.h */, 4F3ED9E3211C27CF0030DD45 /* Info.plist */, ); @@ -128,6 +143,46 @@ path = JailbreakDetect; sourceTree = ""; }; + 907F0FE121DCD1DB001CCB07 /* UINavigationController */ = { + isa = PBXGroup; + children = ( + 907F0FE221DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift */, + ); + path = UINavigationController; + sourceTree = ""; + }; + 907F0FE421DCD375001CCB07 /* SettingsRouter */ = { + isa = PBXGroup; + children = ( + 907F0FE521DCD3A0001CCB07 /* SettingsRouter.swift */, + ); + path = SettingsRouter; + sourceTree = ""; + }; + 907F0FE721DCD5B9001CCB07 /* BlurBuilder */ = { + isa = PBXGroup; + children = ( + 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */, + ); + path = BlurBuilder; + sourceTree = ""; + }; + 907F0FEA21DCD7C6001CCB07 /* RouteMeasurer */ = { + isa = PBXGroup; + children = ( + 907F0FEB21DCD7E1001CCB07 /* RouteMeasurer.swift */, + ); + path = RouteMeasurer; + sourceTree = ""; + }; + 907F0FED21DCDB1F001CCB07 /* WordDeclinationConstructor */ = { + isa = PBXGroup; + children = ( + 907F0FEE21DCDB43001CCB07 /* WordDeclinationConstructor.swift */, + ); + path = WordDeclinationConstructor; + sourceTree = ""; + }; E9B06306214691F30080C391 /* VibrationFeedbackManager */ = { isa = PBXGroup; children = ( @@ -248,13 +303,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 907F0FEC21DCD7E1001CCB07 /* RouteMeasurer.swift in Sources */, 18F2361421D2150200169AC9 /* Dictionary+QueryStringBuilder.swift in Sources */, E9B063102147AECC0080C391 /* UIDevice+feedbackType.swift in Sources */, + 907F0FE921DCD5C5001CCB07 /* BlurBuilder.swift in Sources */, E9B0630A2146924A0080C391 /* VibrationFeedbackManager.swift in Sources */, + 907F0FE621DCD3A0001CCB07 /* SettingsRouter.swift in Sources */, E9B0630C214692C30080C391 /* UIDevice+hasTapticEngine.swift in Sources */, 80437D26214045EF0095A8D0 /* JailbreakDetect.swift in Sources */, E9B0630E214693160080C391 /* UIDevice+hasHapticFeedback.swift in Sources */, 4F3ED9FC211C27FD0030DD45 /* String+Attributes.swift in Sources */, + 907F0FEF21DCDB43001CCB07 /* WordDeclinationConstructor.swift in Sources */, + 907F0FE321DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Utils/Utils/BlurBuilder/BlurBuilder.swift b/Utils/Utils/BlurBuilder/BlurBuilder.swift new file mode 100644 index 0000000..591c953 --- /dev/null +++ b/Utils/Utils/BlurBuilder/BlurBuilder.swift @@ -0,0 +1,26 @@ +// +// BlurBuilder.swift +// Utils +// +// Created by Александр Чаусов on 02/01/2019. +// Copyright © 2019 Surf. All rights reserved. +// + +import UIKit + +public final class BlurBuilder { + + /// Method allows you to add blur with custom color and style on other view + public static func addBlur(on view: UIView, with color: UIColor, style: UIBlurEffect.Style = .dark) { + view.backgroundColor = UIColor.clear + + let blurEffect = UIBlurEffect(style: style) + let blurEffectView = UIVisualEffectView(effect: blurEffect) + blurEffectView.frame = view.bounds + blurEffectView.contentView.backgroundColor = color + blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + view.insertSubview(blurEffectView, at: 0) + } + +} diff --git a/Utils/Utils/RouteMeasurer/RouteMeasurer.swift b/Utils/Utils/RouteMeasurer/RouteMeasurer.swift new file mode 100644 index 0000000..cc28a44 --- /dev/null +++ b/Utils/Utils/RouteMeasurer/RouteMeasurer.swift @@ -0,0 +1,73 @@ +// +// RouteMeasurer.swift +// Utils +// +// Created by Александр Чаусов on 02/01/2019. +// Copyright © 2019 Surf. All rights reserved. +// + +import MapKit + +/// Utils for measure distance between two points and format this distance +public final class RouteMeasurer { + + /// Method returns direct distance between two points + public static func calculateApproximateDistance(between firstPoint: CLLocationCoordinate2D, and secondPoint: CLLocationCoordinate2D) -> Double { + let sourceLocation = CLLocation(latitude: firstPoint.latitude, longitude: firstPoint.longitude) + let destinationLocation = CLLocation(latitude: secondPoint.latitude, longitude: secondPoint.longitude) + return destinationLocation.distance(from: sourceLocation) + } + + /// Method returns nearest route distance between two points, using MKDirections + public static func calculateDistance(between firstPoint: CLLocationCoordinate2D, and secondPoint: CLLocationCoordinate2D, completion: @escaping ((Double?) -> Void)) { + let directionRequest = MKDirectionsRequest() + let sourcePlacemark = MKPlacemark(coordinate: firstPoint, addressDictionary: nil) + let destinationPlacemark = MKPlacemark(coordinate: secondPoint, addressDictionary: nil) + let source = MKMapItem(placemark: sourcePlacemark) + let destination = MKMapItem(placemark: destinationPlacemark) + + directionRequest.source = source + directionRequest.destination = destination + directionRequest.transportType = .any + let directions = MKDirections(request: directionRequest) + + directions.calculate { (response, error) in + guard error == nil else { + completion(nil) + return + } + guard let route = response?.routes.first else { + completion(nil) + return + } + completion(route.distance) + } + } + + /// Method allows you formate distance value, using your pattern for meters and kilometers. + /// You can pass in the method kmBoundaryLevel value, after which the value of kilometers will be rounded to an integer value. + public static func formatDistance(_ distance: Double, meterPattern: String, kilometrPatter: String, kmBoundaryLevel: Double = 50000) -> String { + switch distance { + case ..<0: + return join(distance: "0", pattern: meterPattern) + case ..<1000: + return join(distance: String(format: "%.0f", distance), pattern: meterPattern) + case .. String { + return distance + " " + pattern + } + +} + diff --git a/Utils/Utils/SettingsRouter/SettingsRouter.swift b/Utils/Utils/SettingsRouter/SettingsRouter.swift new file mode 100644 index 0000000..95bc49a --- /dev/null +++ b/Utils/Utils/SettingsRouter/SettingsRouter.swift @@ -0,0 +1,46 @@ +// +// SettingsRouter.swift +// Utils +// +// Created by Александр Чаусов on 02/01/2019. +// Copyright © 2019 Surf. All rights reserved. +// + +import Foundation + +/// Utils for opening various settings screens from the application +public final class SettingsRouter { + + // MARK: - Constants + + private enum Constants { + static let deviceSettingsUrl = "App-prefs:root=General" + } + + // MARK: - Internal Methods + + public static func openAppSettings() { + if let url = URL(string: UIApplicationOpenSettingsURLString) { + openExpectedURL(url) + } + } + + public static func openDeviceSettings() { + if let url = URL(string: Constants.deviceSettingsUrl) { + openExpectedURL(url) + } + } + +} + +// MARK: - Private Methods + +private extension SettingsRouter { + + static func openExpectedURL(_ url: URL) { + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } + } + +} diff --git a/Utils/Utils/UINavigationController/UINavigationController+AdvancedNavigationStackManagement.swift b/Utils/Utils/UINavigationController/UINavigationController+AdvancedNavigationStackManagement.swift new file mode 100644 index 0000000..2289c9d --- /dev/null +++ b/Utils/Utils/UINavigationController/UINavigationController+AdvancedNavigationStackManagement.swift @@ -0,0 +1,41 @@ +// +// UINavigationController+AdvancedNavigationStackManagement.swift +// Utils +// +// Created by Александр Чаусов on 02/01/2019. +// Copyright © 2019 Surf. All rights reserved. +// + +import UIKit + +public extension UINavigationController { + + /// Method for calling pushViewController(_, animated) method with completion closure + func pushViewController(_ viewController: UIViewController, animated: Bool, completion: @escaping () -> Void) { + pushViewController(viewController, animated: animated) + guard animated, let coordinator = transitionCoordinator else { + DispatchQueue.main.async { + completion() + } + return + } + coordinator.animate(alongsideTransition: nil) { _ in + completion() + } + } + + /// Method for calling popViewController(animated) method with completion closure + func popViewController(animated: Bool, completion: @escaping () -> Void) { + popViewController(animated: animated) + guard animated, let coordinator = transitionCoordinator else { + DispatchQueue.main.async { + completion() + } + return + } + coordinator.animate(alongsideTransition: nil) { _ in + completion() + } + } + +} diff --git a/Utils/Utils/WordDeclinationConstructor/WordDeclinationConstructor.swift b/Utils/Utils/WordDeclinationConstructor/WordDeclinationConstructor.swift new file mode 100644 index 0000000..b88b96a --- /dev/null +++ b/Utils/Utils/WordDeclinationConstructor/WordDeclinationConstructor.swift @@ -0,0 +1,53 @@ +// +// WordDeclinationConstructor.swift +// Utils +// +// Created by Александр Чаусов on 02/01/2019. +// Copyright © 2019 Surf. All rights reserved. +// + +import Foundation + +/// An instance of this class is used to generate the correct word declination for a number and contains all the available declensions. +public final class WordDeclensions { + let singularNominative: String + let genetiveSingular: String + let genetivePlural: String + + /// Initialization of the class instance. You have to provide three word declensions. + /// - Parameters: + /// - singularNominative: the word nominative case in the singular, for example "День" + /// - genetiveSingular: the word genitive in the singular, for example "Дня" + /// - genetivePlural: is the word genitive in the plural, for example "Дней" + init(_ singularNominative: String, _ genetiveSingular: String, _ genetivePlural: String) { + self.singularNominative = singularNominative + self.genetiveSingular = genetiveSingular + self.genetivePlural = genetivePlural + } +} + +/// Class for select correct word declension for the passed number +public final class WordDeclinationSelector { + + /// This method is used to get correct declension of word for some number + /// - Parameters: + /// - number: The number to which you want to find the declination. + /// - declensions: An instance of the word declinations. + /// - Returns: Returns a word from the word declensions in the right form. + public static func declineWord(for number: Int, from declensions: WordDeclensions) -> String { + let ending = number % 100 + if (ending >= 11 && ending <= 19) { + return declensions.genetivePlural + } else { + switch (ending % 10) { + case (1): + return declensions.singularNominative + case 2, 3, 4: + return declensions.genetiveSingular + default: + return declensions.genetivePlural + } + } + } + +} From 2fde7b6d3ea2972ea8bcab28c7a63b124d2aecb1 Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Wed, 2 Jan 2019 15:46:44 +0300 Subject: [PATCH 02/13] small fixes in naming --- .../WordDeclinationConstructor.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Utils/Utils/WordDeclinationConstructor/WordDeclinationConstructor.swift b/Utils/Utils/WordDeclinationConstructor/WordDeclinationConstructor.swift index b88b96a..0414039 100644 --- a/Utils/Utils/WordDeclinationConstructor/WordDeclinationConstructor.swift +++ b/Utils/Utils/WordDeclinationConstructor/WordDeclinationConstructor.swift @@ -9,7 +9,7 @@ import Foundation /// An instance of this class is used to generate the correct word declination for a number and contains all the available declensions. -public final class WordDeclensions { +public final class WordDeclinations { let singularNominative: String let genetiveSingular: String let genetivePlural: String @@ -34,7 +34,7 @@ public final class WordDeclinationSelector { /// - number: The number to which you want to find the declination. /// - declensions: An instance of the word declinations. /// - Returns: Returns a word from the word declensions in the right form. - public static func declineWord(for number: Int, from declensions: WordDeclensions) -> String { + public static func declineWord(for number: Int, from declensions: WordDeclinations) -> String { let ending = number % 100 if (ending >= 11 && ending <= 19) { return declensions.genetivePlural From 532194619dbe86720ef65131223b716e4baa5010 Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Wed, 2 Jan 2019 15:54:26 +0300 Subject: [PATCH 03/13] updated podspec file --- SurfUtils.podspec | 27 ++++++++++++++++++- Utils/Utils.xcodeproj/project.pbxproj | 14 +++++----- .../WordDeclinationSelector.swift} | 0 3 files changed, 33 insertions(+), 8 deletions(-) rename Utils/Utils/{WordDeclinationConstructor/WordDeclinationConstructor.swift => WordDeclinationSelector/WordDeclinationSelector.swift} (100%) diff --git a/SurfUtils.podspec b/SurfUtils.podspec index 7b4f8d5..7c0666e 100644 --- a/SurfUtils.podspec +++ b/SurfUtils.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SurfUtils" - s.version = "4.0.0" + s.version = "5.0.0" s.summary = "Contains a set of utils in subspecs" s.description = <<-DESC Contains: @@ -38,4 +38,29 @@ Pod::Spec.new do |s| sp.framework = 'Foundation' end + s.subspec 'BlurBuilder' do |sp| + sp.source_files = 'Utils/Utils/BlurBuilder/BlurBuilder.swift' + sp.framework = 'UIKit' + end + + s.subspec 'RouteMeasurer' do |sp| + sp.source_files = 'Utils/Utils/RouteMeasurer/RouteMeasurer.swift' + sp.framework = 'MapKit' + end + + s.subspec 'SettingsRouter' do |sp| + sp.source_files = 'Utils/Utils/SettingsRouter/SettingsRouter.swift' + sp.framework = 'Foundation' + end + + s.subspec 'AdvancedNavigationStackManagement' do |sp| + sp.source_files = 'Utils/Utils/UINavigationController/UINavigationController+AdvancedNavigationStackManagement.swift' + sp.framework = 'UIKit' + end + + s.subspec 'WordDeclinationSelector' do |sp| + sp.source_files = 'Utils/Utils/WordDeclinationSelector/WordDeclinationSelector.swift' + sp.framework = 'Foundation' + end + end diff --git a/Utils/Utils.xcodeproj/project.pbxproj b/Utils/Utils.xcodeproj/project.pbxproj index 96d6701..74e44ed 100644 --- a/Utils/Utils.xcodeproj/project.pbxproj +++ b/Utils/Utils.xcodeproj/project.pbxproj @@ -17,7 +17,7 @@ 907F0FE621DCD3A0001CCB07 /* SettingsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE521DCD3A0001CCB07 /* SettingsRouter.swift */; }; 907F0FE921DCD5C5001CCB07 /* BlurBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */; }; 907F0FEC21DCD7E1001CCB07 /* RouteMeasurer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FEB21DCD7E1001CCB07 /* RouteMeasurer.swift */; }; - 907F0FEF21DCDB43001CCB07 /* WordDeclinationConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FEE21DCDB43001CCB07 /* WordDeclinationConstructor.swift */; }; + 907F0FEF21DCDB43001CCB07 /* WordDeclinationSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FEE21DCDB43001CCB07 /* WordDeclinationSelector.swift */; }; E9B0630A2146924A0080C391 /* VibrationFeedbackManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B063092146924A0080C391 /* VibrationFeedbackManager.swift */; }; E9B0630C214692C30080C391 /* UIDevice+hasTapticEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B0630B214692C30080C391 /* UIDevice+hasTapticEngine.swift */; }; E9B0630E214693160080C391 /* UIDevice+hasHapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B0630D214693160080C391 /* UIDevice+hasHapticFeedback.swift */; }; @@ -48,7 +48,7 @@ 907F0FE521DCD3A0001CCB07 /* SettingsRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRouter.swift; sourceTree = ""; }; 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurBuilder.swift; sourceTree = ""; }; 907F0FEB21DCD7E1001CCB07 /* RouteMeasurer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteMeasurer.swift; sourceTree = ""; }; - 907F0FEE21DCDB43001CCB07 /* WordDeclinationConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordDeclinationConstructor.swift; sourceTree = ""; }; + 907F0FEE21DCDB43001CCB07 /* WordDeclinationSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordDeclinationSelector.swift; sourceTree = ""; }; E9B063092146924A0080C391 /* VibrationFeedbackManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationFeedbackManager.swift; sourceTree = ""; }; E9B0630B214692C30080C391 /* UIDevice+hasTapticEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+hasTapticEngine.swift"; sourceTree = ""; }; E9B0630D214693160080C391 /* UIDevice+hasHapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+hasHapticFeedback.swift"; sourceTree = ""; }; @@ -111,7 +111,7 @@ 4F3ED9FA211C27E80030DD45 /* String */, 907F0FE121DCD1DB001CCB07 /* UINavigationController */, E9B06306214691F30080C391 /* VibrationFeedbackManager */, - 907F0FED21DCDB1F001CCB07 /* WordDeclinationConstructor */, + 907F0FED21DCDB1F001CCB07 /* WordDeclinationSelector */, 4F3ED9E2211C27CF0030DD45 /* Utils.h */, 4F3ED9E3211C27CF0030DD45 /* Info.plist */, ); @@ -175,12 +175,12 @@ path = RouteMeasurer; sourceTree = ""; }; - 907F0FED21DCDB1F001CCB07 /* WordDeclinationConstructor */ = { + 907F0FED21DCDB1F001CCB07 /* WordDeclinationSelector */ = { isa = PBXGroup; children = ( - 907F0FEE21DCDB43001CCB07 /* WordDeclinationConstructor.swift */, + 907F0FEE21DCDB43001CCB07 /* WordDeclinationSelector.swift */, ); - path = WordDeclinationConstructor; + path = WordDeclinationSelector; sourceTree = ""; }; E9B06306214691F30080C391 /* VibrationFeedbackManager */ = { @@ -313,7 +313,7 @@ 80437D26214045EF0095A8D0 /* JailbreakDetect.swift in Sources */, E9B0630E214693160080C391 /* UIDevice+hasHapticFeedback.swift in Sources */, 4F3ED9FC211C27FD0030DD45 /* String+Attributes.swift in Sources */, - 907F0FEF21DCDB43001CCB07 /* WordDeclinationConstructor.swift in Sources */, + 907F0FEF21DCDB43001CCB07 /* WordDeclinationSelector.swift in Sources */, 907F0FE321DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Utils/Utils/WordDeclinationConstructor/WordDeclinationConstructor.swift b/Utils/Utils/WordDeclinationSelector/WordDeclinationSelector.swift similarity index 100% rename from Utils/Utils/WordDeclinationConstructor/WordDeclinationConstructor.swift rename to Utils/Utils/WordDeclinationSelector/WordDeclinationSelector.swift From 1adc185f6d86d30180559c62fdd0bd9fc1f94a92 Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Wed, 2 Jan 2019 16:16:33 +0300 Subject: [PATCH 04/13] updated readme file --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/README.md b/README.md index d169592..a3d6011 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ pod 'SurfUtils/$UTIL_NAME$', :git => "https://github.com/surfstudio/iOS-Utils.gi - [JailbreakDetect](#jailbreakdetect) - позволяет определить наличие root на девайсе. - [VibrationFeedbackManager](#vibrationfeedbackmanager) - позволяет воспроизвести вибрацию на устройстве. - [QueryStringBuilder](#querystringbuilder) - построение строки с параметрами из словаря +- [BlurBuilder](#blurbuilder) - упрощение работы с blur-эффектом +- [RouteMeasurer](#routemeasurer) - вычисление расстояния между двумя координатами +- [SettingsRouter](#settingsrouter) - позволяет выполнить переход в настройки приложения/устройства +- [AdvancedNavigationStackManagement](#advancednavigationstackmanagement) - расширенная версия методов push/pop у UINavigationController +- [WordDeclinationSelector](#worddeclinationselector) - позволяет получить нужное склонение слова ## Утилиты @@ -65,6 +70,58 @@ let dict: [String: Any] = ["key1": "value1", "key2": 2.15, "key3": true] let queryString = dict.toQueryString() ``` +### BlurBuilder + +Утилита для упрощения добавления стандартного блюра на какое-либо View, позволяет управлять стилем и цветом блюра. + +Пример: +```Swift +BlurBuilder.addBlur(on: bluredView, with: UIColor.white.withAlphaComponent(0.1), style: .light) +``` + +### RouteMeasurer + +Утилита для вычисления расстояния между двумя точками, как напрямую, так и с учетом возможного маршрута. Помимо прочего, предоставляет метод для форматирования результата. + +Пример: +```Swift +RouteMeasurer.calculateDistance(between: firstCoordinate, and: secondCoordinate) { (distance) in + guard let distance = distance else { + return + } + let formattedDistance = RouteMeasurer.formatDistance(distance, meterPattern: "м", kilometrPatter: "км")) +} +``` + +### SettingsRouter + +Утилита для упрощения перехода к настройкам приложения или к конкретному разделу настроек устройства. + +Пример: +```Swift +SettingsRouter.openDeviceSettings() +``` + +### AdvancedNavigationStackManagement + +Данная утилита предоставляет возможность вызова методов push и pop у UINavigationController с последующим вызывом completion-замыкания после завершения действия. + +Пример: +```Swift +navigationController?.pushViewController(controller, animated: true, completion: { + print("do something else") +}) +``` + +### WordDeclinationSelector + +Утилита, позволяющая получить верное склонение слово в зависимости от числа элементов. + +Пример: +```Swift +let correctForm = WordDeclinationSelector.declineWord(for: 6, from: WordDeclensions("день", "дня", "дней")) +``` + ## Версионирование В качестве принципа версионирования используется [Семантическое версионирования (Semantic Versioning)](https://semver.org/). From 23caecf278a752c1fb2e306699080e1dadd87f27 Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Wed, 2 Jan 2019 17:26:08 +0300 Subject: [PATCH 05/13] fixed misprint in readme file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3d6011..cf52076 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ navigationController?.pushViewController(controller, animated: true, completion: ### WordDeclinationSelector -Утилита, позволяющая получить верное склонение слово в зависимости от числа элементов. +Утилита, позволяющая получить верное склонение слова в зависимости от числа элементов. Пример: ```Swift From 2744c847c29392087a51b5c8bfd0cecbaf200813 Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Sat, 5 Jan 2019 12:34:26 +0300 Subject: [PATCH 06/13] fixes by comments from PR, added more comments and example of usage, fixed code style --- Utils/Utils/BlurBuilder/BlurBuilder.swift | 12 ++++++- Utils/Utils/RouteMeasurer/RouteMeasurer.swift | 35 ++++++++++--------- .../Utils/SettingsRouter/SettingsRouter.swift | 17 +++++---- .../WordDeclinationSelector.swift | 29 ++++++++------- 4 files changed, 56 insertions(+), 37 deletions(-) diff --git a/Utils/Utils/BlurBuilder/BlurBuilder.swift b/Utils/Utils/BlurBuilder/BlurBuilder.swift index 591c953..c5aacca 100644 --- a/Utils/Utils/BlurBuilder/BlurBuilder.swift +++ b/Utils/Utils/BlurBuilder/BlurBuilder.swift @@ -10,7 +10,17 @@ import UIKit public final class BlurBuilder { - /// Method allows you to add blur with custom color and style on other view + /// Method allows you to add blur with custom color and style on needed view + /// + /// Example of usage: + /// ``` + /// BlurBuilder.addBlur(on: bluredView, with: UIColor.white.withAlphaComponent(0.1), style: .light) + /// ``` + /// + /// - Parameters: + /// - view: View to which the blur will be added + /// - color: Color of the blur effect + /// - style: Style of the blur effect, default value is .dark public static func addBlur(on view: UIView, with color: UIColor, style: UIBlurEffect.Style = .dark) { view.backgroundColor = UIColor.clear diff --git a/Utils/Utils/RouteMeasurer/RouteMeasurer.swift b/Utils/Utils/RouteMeasurer/RouteMeasurer.swift index cc28a44..bf3006a 100644 --- a/Utils/Utils/RouteMeasurer/RouteMeasurer.swift +++ b/Utils/Utils/RouteMeasurer/RouteMeasurer.swift @@ -44,30 +44,31 @@ public final class RouteMeasurer { } } - /// Method allows you formate distance value, using your pattern for meters and kilometers. - /// You can pass in the method kmBoundaryLevel value, after which the value of kilometers will be rounded to an integer value. + /// Formats distance value using your pattern for meters and kilometers. + /// + /// Example of usage: + /// ``` + /// print(formatDistance(1231, meterPattern: "m", kilometrPattern: "km")) + /// // Prints "12,3 km" + /// ``` + /// + /// - Parameters: + /// - distance: Distance in meters that should be formatted + /// - meterPattern: Pattern for meters formatting + /// - kilometrPatter: Pattern for kilometers formatting + /// - kmBoundaryLevel: Pass in kmBoundaryLevel value, after which the value of kilometers will be rounded to an integer value. Default value is 50000 + /// - Returns: Formatted distance string public static func formatDistance(_ distance: Double, meterPattern: String, kilometrPatter: String, kmBoundaryLevel: Double = 50000) -> String { switch distance { case ..<0: - return join(distance: "0", pattern: meterPattern) + return ["0", meterPattern].joined(separator: " ") case ..<1000: - return join(distance: String(format: "%.0f", distance), pattern: meterPattern) + return [String(format: "%.0f", distance), meterPattern].joined(separator: " ") case .. String { - return distance + " " + pattern - } - -} - diff --git a/Utils/Utils/SettingsRouter/SettingsRouter.swift b/Utils/Utils/SettingsRouter/SettingsRouter.swift index 95bc49a..f335633 100644 --- a/Utils/Utils/SettingsRouter/SettingsRouter.swift +++ b/Utils/Utils/SettingsRouter/SettingsRouter.swift @@ -17,18 +17,20 @@ public final class SettingsRouter { static let deviceSettingsUrl = "App-prefs:root=General" } - // MARK: - Internal Methods + // MARK: - Public Methods public static func openAppSettings() { - if let url = URL(string: UIApplicationOpenSettingsURLString) { - openExpectedURL(url) + guard let url = URL(string: UIApplicationOpenSettingsURLString) else { + return } + openExpectedURL(url) } public static func openDeviceSettings() { - if let url = URL(string: Constants.deviceSettingsUrl) { - openExpectedURL(url) + guard let url = URL(string: Constants.deviceSettingsUrl) else { + return } + openExpectedURL(url) } } @@ -38,9 +40,10 @@ public final class SettingsRouter { private extension SettingsRouter { static func openExpectedURL(_ url: URL) { - if UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url, options: [:], completionHandler: nil) + guard UIApplication.shared.canOpenURL(url) else { + return } + UIApplication.shared.open(url, options: [:], completionHandler: nil) } } diff --git a/Utils/Utils/WordDeclinationSelector/WordDeclinationSelector.swift b/Utils/Utils/WordDeclinationSelector/WordDeclinationSelector.swift index 0414039..9186f11 100644 --- a/Utils/Utils/WordDeclinationSelector/WordDeclinationSelector.swift +++ b/Utils/Utils/WordDeclinationSelector/WordDeclinationSelector.swift @@ -16,9 +16,9 @@ public final class WordDeclinations { /// Initialization of the class instance. You have to provide three word declensions. /// - Parameters: - /// - singularNominative: the word nominative case in the singular, for example "День" - /// - genetiveSingular: the word genitive in the singular, for example "Дня" - /// - genetivePlural: is the word genitive in the plural, for example "Дней" + /// - singularNominative: The word nominative case in the singular, for example "День" + /// - genetiveSingular: The word genitive in the singular, for example "Дня" + /// - genetivePlural: The word genitive in the plural, for example "Дней" init(_ singularNominative: String, _ genetiveSingular: String, _ genetivePlural: String) { self.singularNominative = singularNominative self.genetiveSingular = genetiveSingular @@ -30,6 +30,12 @@ public final class WordDeclinations { public final class WordDeclinationSelector { /// This method is used to get correct declension of word for some number + /// + /// Example of usage: + /// ``` + /// let correctForm = WordDeclinationSelector.declineWord(for: 6, from: WordDeclensions("день", "дня", "дней")) + /// ``` + /// /// - Parameters: /// - number: The number to which you want to find the declination. /// - declensions: An instance of the word declinations. @@ -38,15 +44,14 @@ public final class WordDeclinationSelector { let ending = number % 100 if (ending >= 11 && ending <= 19) { return declensions.genetivePlural - } else { - switch (ending % 10) { - case (1): - return declensions.singularNominative - case 2, 3, 4: - return declensions.genetiveSingular - default: - return declensions.genetivePlural - } + } + switch (ending % 10) { + case (1): + return declensions.singularNominative + case 2, 3, 4: + return declensions.genetiveSingular + default: + return declensions.genetivePlural } } From 285ac3c9b38a3068e01e1d39b790288f930247d0 Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Sat, 5 Jan 2019 12:47:04 +0300 Subject: [PATCH 07/13] added tests for word declination selector --- Utils/Utils.xcodeproj/project.pbxproj | 4 ++ .../WordDeclinationSelectorTests.swift | 45 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 Utils/UtilsTests/WordDeclinationSelectorTests.swift diff --git a/Utils/Utils.xcodeproj/project.pbxproj b/Utils/Utils.xcodeproj/project.pbxproj index 74e44ed..e9b0b5b 100644 --- a/Utils/Utils.xcodeproj/project.pbxproj +++ b/Utils/Utils.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 907F0FE921DCD5C5001CCB07 /* BlurBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */; }; 907F0FEC21DCD7E1001CCB07 /* RouteMeasurer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FEB21DCD7E1001CCB07 /* RouteMeasurer.swift */; }; 907F0FEF21DCDB43001CCB07 /* WordDeclinationSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FEE21DCDB43001CCB07 /* WordDeclinationSelector.swift */; }; + 90C101C821E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C101C721E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift */; }; E9B0630A2146924A0080C391 /* VibrationFeedbackManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B063092146924A0080C391 /* VibrationFeedbackManager.swift */; }; E9B0630C214692C30080C391 /* UIDevice+hasTapticEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B0630B214692C30080C391 /* UIDevice+hasTapticEngine.swift */; }; E9B0630E214693160080C391 /* UIDevice+hasHapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B0630D214693160080C391 /* UIDevice+hasHapticFeedback.swift */; }; @@ -49,6 +50,7 @@ 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurBuilder.swift; sourceTree = ""; }; 907F0FEB21DCD7E1001CCB07 /* RouteMeasurer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteMeasurer.swift; sourceTree = ""; }; 907F0FEE21DCDB43001CCB07 /* WordDeclinationSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordDeclinationSelector.swift; sourceTree = ""; }; + 90C101C721E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordDeclinationSelectorTests.swift; sourceTree = ""; }; E9B063092146924A0080C391 /* VibrationFeedbackManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationFeedbackManager.swift; sourceTree = ""; }; E9B0630B214692C30080C391 /* UIDevice+hasTapticEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+hasTapticEngine.swift"; sourceTree = ""; }; E9B0630D214693160080C391 /* UIDevice+hasHapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+hasHapticFeedback.swift"; sourceTree = ""; }; @@ -122,6 +124,7 @@ isa = PBXGroup; children = ( 4F3ED9ED211C27CF0030DD45 /* UtilsTests.swift */, + 90C101C721E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift */, 4F3ED9EF211C27CF0030DD45 /* Info.plist */, ); path = UtilsTests; @@ -323,6 +326,7 @@ buildActionMask = 2147483647; files = ( 4F3ED9EE211C27CF0030DD45 /* UtilsTests.swift in Sources */, + 90C101C821E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Utils/UtilsTests/WordDeclinationSelectorTests.swift b/Utils/UtilsTests/WordDeclinationSelectorTests.swift new file mode 100644 index 0000000..a88c204 --- /dev/null +++ b/Utils/UtilsTests/WordDeclinationSelectorTests.swift @@ -0,0 +1,45 @@ +// +// WordDeclinationSelectorTests.swift +// UtilsTests +// +// Created by Александр Чаусов on 05/01/2019. +// Copyright © 2019 Surf. All rights reserved. +// + +import XCTest +@testable import Utils + +final class WordDeclinationSelectorTests: XCTestCase { + + let form1 = "книга" + let form2 = "книги" + let form3 = "книг" + + func testDeclinationFor11_19() { + testWordDeclinationSelector(sequence: [11, 12, 13, 14, 15, 16, 17, 18, 19, 115, 1018, 10912], correctForm: form3) + } + + func testDeclinationForEndsIn1() { + testWordDeclinationSelector(sequence: [1, 31, 141, 1941, 1081], correctForm: form1) + } + + func testDeclinationForEndsIn2_4() { + testWordDeclinationSelector(sequence: [2, 23, 54, 142, 5683, 49654], correctForm: form2) + } + + func testDeclinationForEndsIn5_0() { + testWordDeclinationSelector(sequence: [0, 5, 6, 7, 8, 9, 20, 50, 75, 106, 4267, 1088, 55439, 100000], correctForm: form3) + } + +} + +private extension WordDeclinationSelectorTests { + + func testWordDeclinationSelector(sequence: S, correctForm: String) { + for number in sequence.compactMap({ $0 as? Int }) { + let result = WordDeclinationSelector.declineWord(for: number, from: WordDeclinations(form1, form2, form3)) + XCTAssertEqual(result, correctForm) + } + } + +} From 8243894a2c46944d093c4e6b07b53643fd01eac1 Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Sat, 5 Jan 2019 13:01:00 +0300 Subject: [PATCH 08/13] added tests for route measurer format distance method --- Utils/Utils.xcodeproj/project.pbxproj | 8 ++-- Utils/UtilsTests/RouteMeasurerTests.swift | 46 +++++++++++++++++++ Utils/UtilsTests/UtilsTests.swift | 36 --------------- .../WordDeclinationSelectorTests.swift | 10 ++-- 4 files changed, 57 insertions(+), 43 deletions(-) create mode 100644 Utils/UtilsTests/RouteMeasurerTests.swift delete mode 100644 Utils/UtilsTests/UtilsTests.swift diff --git a/Utils/Utils.xcodeproj/project.pbxproj b/Utils/Utils.xcodeproj/project.pbxproj index e9b0b5b..7d19997 100644 --- a/Utils/Utils.xcodeproj/project.pbxproj +++ b/Utils/Utils.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 18F2361421D2150200169AC9 /* Dictionary+QueryStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2361321D2150200169AC9 /* Dictionary+QueryStringBuilder.swift */; }; 4F3ED9E9211C27CF0030DD45 /* Utils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F3ED9DF211C27CF0030DD45 /* Utils.framework */; }; - 4F3ED9EE211C27CF0030DD45 /* UtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3ED9ED211C27CF0030DD45 /* UtilsTests.swift */; }; 4F3ED9F0211C27CF0030DD45 /* Utils.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F3ED9E2211C27CF0030DD45 /* Utils.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4F3ED9FC211C27FD0030DD45 /* String+Attributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3ED9FB211C27FD0030DD45 /* String+Attributes.swift */; }; 80437D26214045EF0095A8D0 /* JailbreakDetect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80437D25214045EF0095A8D0 /* JailbreakDetect.swift */; }; @@ -19,6 +18,7 @@ 907F0FEC21DCD7E1001CCB07 /* RouteMeasurer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FEB21DCD7E1001CCB07 /* RouteMeasurer.swift */; }; 907F0FEF21DCDB43001CCB07 /* WordDeclinationSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FEE21DCDB43001CCB07 /* WordDeclinationSelector.swift */; }; 90C101C821E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C101C721E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift */; }; + 90C101CA21E0B62D002E8E65 /* RouteMeasurerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C101C921E0B62D002E8E65 /* RouteMeasurerTests.swift */; }; E9B0630A2146924A0080C391 /* VibrationFeedbackManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B063092146924A0080C391 /* VibrationFeedbackManager.swift */; }; E9B0630C214692C30080C391 /* UIDevice+hasTapticEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B0630B214692C30080C391 /* UIDevice+hasTapticEngine.swift */; }; E9B0630E214693160080C391 /* UIDevice+hasHapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B0630D214693160080C391 /* UIDevice+hasHapticFeedback.swift */; }; @@ -41,7 +41,6 @@ 4F3ED9E2211C27CF0030DD45 /* Utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Utils.h; sourceTree = ""; }; 4F3ED9E3211C27CF0030DD45 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4F3ED9E8211C27CF0030DD45 /* UtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 4F3ED9ED211C27CF0030DD45 /* UtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilsTests.swift; sourceTree = ""; }; 4F3ED9EF211C27CF0030DD45 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4F3ED9FB211C27FD0030DD45 /* String+Attributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Attributes.swift"; sourceTree = ""; }; 80437D25214045EF0095A8D0 /* JailbreakDetect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JailbreakDetect.swift; sourceTree = ""; }; @@ -51,6 +50,7 @@ 907F0FEB21DCD7E1001CCB07 /* RouteMeasurer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteMeasurer.swift; sourceTree = ""; }; 907F0FEE21DCDB43001CCB07 /* WordDeclinationSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordDeclinationSelector.swift; sourceTree = ""; }; 90C101C721E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordDeclinationSelectorTests.swift; sourceTree = ""; }; + 90C101C921E0B62D002E8E65 /* RouteMeasurerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteMeasurerTests.swift; sourceTree = ""; }; E9B063092146924A0080C391 /* VibrationFeedbackManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrationFeedbackManager.swift; sourceTree = ""; }; E9B0630B214692C30080C391 /* UIDevice+hasTapticEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+hasTapticEngine.swift"; sourceTree = ""; }; E9B0630D214693160080C391 /* UIDevice+hasHapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+hasHapticFeedback.swift"; sourceTree = ""; }; @@ -123,7 +123,7 @@ 4F3ED9EC211C27CF0030DD45 /* UtilsTests */ = { isa = PBXGroup; children = ( - 4F3ED9ED211C27CF0030DD45 /* UtilsTests.swift */, + 90C101C921E0B62D002E8E65 /* RouteMeasurerTests.swift */, 90C101C721E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift */, 4F3ED9EF211C27CF0030DD45 /* Info.plist */, ); @@ -325,7 +325,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4F3ED9EE211C27CF0030DD45 /* UtilsTests.swift in Sources */, + 90C101CA21E0B62D002E8E65 /* RouteMeasurerTests.swift in Sources */, 90C101C821E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Utils/UtilsTests/RouteMeasurerTests.swift b/Utils/UtilsTests/RouteMeasurerTests.swift new file mode 100644 index 0000000..274c2c1 --- /dev/null +++ b/Utils/UtilsTests/RouteMeasurerTests.swift @@ -0,0 +1,46 @@ +// +// RouteMeasurerTests.swift +// UtilsTests +// +// Created by Александр Чаусов on 05/01/2019. +// Copyright © 2019 Surf. All rights reserved. +// + +import XCTest +@testable import Utils + +final class RouteMeasurerTests: XCTestCase { + + // MARK: - Constants + + private let meterPattern = "м" + private let kilometrPattern = "км" + + // MARK: - Tests + + func testNegativeDistanceFormat() { + let result = RouteMeasurer.formatDistance(-5, meterPattern: meterPattern, kilometrPatter: kilometrPattern) + XCTAssertEqual(result, "0 " + meterPattern) + } + + func testZeroDistanceFormat() { + let result = RouteMeasurer.formatDistance(0, meterPattern: meterPattern, kilometrPatter: kilometrPattern) + XCTAssertEqual(result, "0 " + meterPattern) + } + + func testSmallDistanceFormat() { + let result = RouteMeasurer.formatDistance(123, meterPattern: meterPattern, kilometrPatter: kilometrPattern) + XCTAssertEqual(result, "123 " + meterPattern) + } + + func testBigDistanceFormat() { + let result = RouteMeasurer.formatDistance(1234, meterPattern: meterPattern, kilometrPatter: kilometrPattern) + XCTAssertEqual(result, "12,3 " + kilometrPattern) + } + + func testHugeDistanceFormat() { + let result = RouteMeasurer.formatDistance(104523, meterPattern: meterPattern, kilometrPatter: kilometrPattern) + XCTAssertEqual(result, "104 " + kilometrPattern) + } + +} diff --git a/Utils/UtilsTests/UtilsTests.swift b/Utils/UtilsTests/UtilsTests.swift deleted file mode 100644 index e554a6a..0000000 --- a/Utils/UtilsTests/UtilsTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// UtilsTests.swift -// UtilsTests -// -// Created by Alexander Kravchenkov on 09.08.2018. -// Copyright © 2018 Surf. All rights reserved. -// - -import XCTest -@testable import Utils - -class UtilsTests: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/Utils/UtilsTests/WordDeclinationSelectorTests.swift b/Utils/UtilsTests/WordDeclinationSelectorTests.swift index a88c204..6d39d9b 100644 --- a/Utils/UtilsTests/WordDeclinationSelectorTests.swift +++ b/Utils/UtilsTests/WordDeclinationSelectorTests.swift @@ -11,9 +11,13 @@ import XCTest final class WordDeclinationSelectorTests: XCTestCase { - let form1 = "книга" - let form2 = "книги" - let form3 = "книг" + // MARK: - Constants + + private let form1 = "книга" + private let form2 = "книги" + private let form3 = "книг" + + // MARK: - Tests func testDeclinationFor11_19() { testWordDeclinationSelector(sequence: [11, 12, 13, 14, 15, 16, 17, 18, 19, 115, 1018, 10912], correctForm: form3) From e1b57cbcf998b583f0f6c49155175ddfd8b64a7b Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Sat, 5 Jan 2019 13:04:56 +0300 Subject: [PATCH 09/13] fixed some tests and method comment --- Utils/Utils/RouteMeasurer/RouteMeasurer.swift | 4 ++-- Utils/UtilsTests/RouteMeasurerTests.swift | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Utils/Utils/RouteMeasurer/RouteMeasurer.swift b/Utils/Utils/RouteMeasurer/RouteMeasurer.swift index bf3006a..cf377d9 100644 --- a/Utils/Utils/RouteMeasurer/RouteMeasurer.swift +++ b/Utils/Utils/RouteMeasurer/RouteMeasurer.swift @@ -48,8 +48,8 @@ public final class RouteMeasurer { /// /// Example of usage: /// ``` - /// print(formatDistance(1231, meterPattern: "m", kilometrPattern: "km")) - /// // Prints "12,3 km" + /// print(formatDistance(12310, meterPattern: "m", kilometrPattern: "km")) + /// // Prints "12.3 km" /// ``` /// /// - Parameters: diff --git a/Utils/UtilsTests/RouteMeasurerTests.swift b/Utils/UtilsTests/RouteMeasurerTests.swift index 274c2c1..f2b99b3 100644 --- a/Utils/UtilsTests/RouteMeasurerTests.swift +++ b/Utils/UtilsTests/RouteMeasurerTests.swift @@ -34,12 +34,12 @@ final class RouteMeasurerTests: XCTestCase { } func testBigDistanceFormat() { - let result = RouteMeasurer.formatDistance(1234, meterPattern: meterPattern, kilometrPatter: kilometrPattern) - XCTAssertEqual(result, "12,3 " + kilometrPattern) + let result = RouteMeasurer.formatDistance(12310, meterPattern: meterPattern, kilometrPatter: kilometrPattern) + XCTAssertEqual(result, "12.3 " + kilometrPattern) } func testHugeDistanceFormat() { - let result = RouteMeasurer.formatDistance(104523, meterPattern: meterPattern, kilometrPatter: kilometrPattern) + let result = RouteMeasurer.formatDistance(104123, meterPattern: meterPattern, kilometrPatter: kilometrPattern) XCTAssertEqual(result, "104 " + kilometrPattern) } From 9dfa626c2273a578690edb45e9b4b73a2fc10cec Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Tue, 8 Jan 2019 15:52:33 +0300 Subject: [PATCH 10/13] add util for carousel elements scroll based on collection view --- Utils/Utils.xcodeproj/project.pbxproj | 12 ++++ .../ItemsScrollManager.swift | 63 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift diff --git a/Utils/Utils.xcodeproj/project.pbxproj b/Utils/Utils.xcodeproj/project.pbxproj index 7d19997..bcf544e 100644 --- a/Utils/Utils.xcodeproj/project.pbxproj +++ b/Utils/Utils.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 4F3ED9F0211C27CF0030DD45 /* Utils.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F3ED9E2211C27CF0030DD45 /* Utils.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4F3ED9FC211C27FD0030DD45 /* String+Attributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3ED9FB211C27FD0030DD45 /* String+Attributes.swift */; }; 80437D26214045EF0095A8D0 /* JailbreakDetect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80437D25214045EF0095A8D0 /* JailbreakDetect.swift */; }; + 902C67EC21E4D20B007B13CC /* ItemsScrollManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 902C67EB21E4D20B007B13CC /* ItemsScrollManager.swift */; }; 907F0FE321DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE221DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift */; }; 907F0FE621DCD3A0001CCB07 /* SettingsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE521DCD3A0001CCB07 /* SettingsRouter.swift */; }; 907F0FE921DCD5C5001CCB07 /* BlurBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */; }; @@ -44,6 +45,7 @@ 4F3ED9EF211C27CF0030DD45 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4F3ED9FB211C27FD0030DD45 /* String+Attributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Attributes.swift"; sourceTree = ""; }; 80437D25214045EF0095A8D0 /* JailbreakDetect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JailbreakDetect.swift; sourceTree = ""; }; + 902C67EB21E4D20B007B13CC /* ItemsScrollManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsScrollManager.swift; sourceTree = ""; }; 907F0FE221DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+AdvancedNavigationStackManagement.swift"; sourceTree = ""; }; 907F0FE521DCD3A0001CCB07 /* SettingsRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRouter.swift; sourceTree = ""; }; 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurBuilder.swift; sourceTree = ""; }; @@ -107,6 +109,7 @@ children = ( 907F0FE721DCD5B9001CCB07 /* BlurBuilder */, 18F2361221D214CC00169AC9 /* Dictionary */, + 902C67EA21E4D1EF007B13CC /* ItemsScrollManager */, 80437D24214045B30095A8D0 /* JailbreakDetect */, 907F0FEA21DCD7C6001CCB07 /* RouteMeasurer */, 907F0FE421DCD375001CCB07 /* SettingsRouter */, @@ -146,6 +149,14 @@ path = JailbreakDetect; sourceTree = ""; }; + 902C67EA21E4D1EF007B13CC /* ItemsScrollManager */ = { + isa = PBXGroup; + children = ( + 902C67EB21E4D20B007B13CC /* ItemsScrollManager.swift */, + ); + path = ItemsScrollManager; + sourceTree = ""; + }; 907F0FE121DCD1DB001CCB07 /* UINavigationController */ = { isa = PBXGroup; children = ( @@ -310,6 +321,7 @@ 18F2361421D2150200169AC9 /* Dictionary+QueryStringBuilder.swift in Sources */, E9B063102147AECC0080C391 /* UIDevice+feedbackType.swift in Sources */, 907F0FE921DCD5C5001CCB07 /* BlurBuilder.swift in Sources */, + 902C67EC21E4D20B007B13CC /* ItemsScrollManager.swift in Sources */, E9B0630A2146924A0080C391 /* VibrationFeedbackManager.swift in Sources */, 907F0FE621DCD3A0001CCB07 /* SettingsRouter.swift in Sources */, E9B0630C214692C30080C391 /* UIDevice+hasTapticEngine.swift in Sources */, diff --git a/Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift b/Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift new file mode 100644 index 0000000..661fcb9 --- /dev/null +++ b/Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift @@ -0,0 +1,63 @@ +// +// ItemsScrollManager.swift +// Utils +// +// Created by Александр Чаусов on 08/01/2019. +// Copyright © 2019 Surf. All rights reserved. +// + +import UIKit + +public final class ItemsScrollManager { + + // MARK: - Private Properties + + private let cellWidth: CGFloat + private let cellOffset: CGFloat + private let insets: UIEdgeInsets + private var containerWidth: CGFloat + + private var beginDraggingOffset: CGFloat = 0 + private var lastOffset: CGFloat = 0 + private var currentPage: Int = 0 + + // MARK: - Initialization + + public init(cellWidth: CGFloat, cellOffset: CGFloat, insets: UIEdgeInsets, containerWidth: CGFloat = UIScreen.main.bounds.width) { + self.cellWidth = cellWidth + self.cellOffset = cellOffset + self.insets = insets + self.containerWidth = containerWidth + } + + // MARK: - Public Methods + + public func setBeginDraggingOffset(_ contentOffsetX: CGFloat) { + beginDraggingOffset = contentOffsetX + } + + public func setTargetContentOffset(_ targetContentOffset: UnsafeMutablePointer, for scrollView: UIScrollView) { + let pageWidth = cellWidth + cellOffset + let firstCellOffset = insets.left - cellOffset + var targetX = targetContentOffset.pointee.x + var pageOffset: CGFloat = 0 + + if beginDraggingOffset == targetX && scrollView.isDecelerating { + // If we just tap somewhere we will not scroll to this point. We will use last offset + targetX = lastOffset + } + + if lastOffset > targetX { + currentPage = Int(max(floor((targetX - firstCellOffset) / pageWidth), 0)) + } else if lastOffset < targetX { + let targetOffset = max(targetX - firstCellOffset, 1) + currentPage = Int(ceil(targetOffset / pageWidth)) + } + + pageOffset = currentPage == 0 ? 0 : CGFloat(currentPage) * pageWidth + firstCellOffset + pageOffset = min(scrollView.contentSize.width - containerWidth, pageOffset) + lastOffset = pageOffset + targetContentOffset.pointee.x = pageOffset + } + +} From a57df9545423000d5861c1a893dcc149078cd297 Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Tue, 8 Jan 2019 16:15:42 +0300 Subject: [PATCH 11/13] detailed comments added --- .../ItemsScrollManager.swift | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift b/Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift index 661fcb9..0272711 100644 --- a/Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift +++ b/Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift @@ -8,6 +8,30 @@ import UIKit +/// Manager allows you to organize the scroll inside the carousel in such a way that the beginning of a new element always appears on the left of the screen. +/// To organize a scroll, it is enough to create an instance of the manager and call two of its methods at the necessary points described in the example below. +/// +/// Example of usage: +/// ``` +/// // Create manager +/// scrollManager = ItemsScrollManager(cellWidth: 200, +/// cellOffset: 10, +/// insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)) +/// +/// // And call two of its methods inside the UISCrollViewDelegate methods +/// extension ViewController: UIScrollViewDelegate { +/// +/// func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { +/// scrollManager?.setTargetContentOffset(targetContentOffset, for: scrollView) +/// } +/// +/// func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { +/// scrollManager?.setBeginDraggingOffset(scrollView.contentOffset.x) +/// } +/// +/// } +/// +/// ``` public final class ItemsScrollManager { // MARK: - Private Properties @@ -23,6 +47,20 @@ public final class ItemsScrollManager { // MARK: - Initialization + /// Init method for the manager. + /// + /// Example of usage: + /// ``` + /// scrollManager = ItemsScrollManager(cellWidth: 200, + /// cellOffset: 10, + /// insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)) + /// ``` + /// + /// - Parameters: + /// - cellWidth: Items cell width + /// - cellOffset: Inter item space between two items inside the carousel + /// - insets: Insets for section with carousel items in collection view + /// - containerWidth: Carousel width, by default equal to screen width public init(cellWidth: CGFloat, cellOffset: CGFloat, insets: UIEdgeInsets, containerWidth: CGFloat = UIScreen.main.bounds.width) { self.cellWidth = cellWidth self.cellOffset = cellOffset @@ -32,10 +70,35 @@ public final class ItemsScrollManager { // MARK: - Public Methods + /// This method is used for setup begin dragging offset, when user start dragging scroll view. + /// You have to call this method inside UIScrollViewDelegate method scrollViewWillBeginDragging(...) + /// + /// Example of usage: + /// ``` + /// func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + /// scrollManager?.setBeginDraggingOffset(scrollView.contentOffset.x) + /// } + /// ``` + /// + /// - Parameters: + /// - contentOffsetX: Scroll view content offset by X axis public func setBeginDraggingOffset(_ contentOffsetX: CGFloat) { beginDraggingOffset = contentOffsetX } + /// This is main method, it used for update scroll view targetContentOffset, when user end dragging scroll view. + /// You have to call this method inside UIScrollViewDelegate method scrollViewWillEndDragging(...) + /// + /// Example of usage: + /// ``` + /// func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + /// scrollManager?.setTargetContentOffset(targetContentOffset, for: scrollView) + /// } + /// ``` + /// + /// - Parameters: + /// - targetContentOffset: Scroll view targetContentOffset from delegate method scrollViewWillEndDragging(...) + /// - scrollView: Scroll view with carousel public func setTargetContentOffset(_ targetContentOffset: UnsafeMutablePointer, for scrollView: UIScrollView) { let pageWidth = cellWidth + cellOffset let firstCellOffset = insets.left - cellOffset From edd085769b6543711287a23ab8eb15c0c50e20c1 Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Tue, 8 Jan 2019 16:29:42 +0300 Subject: [PATCH 12/13] update readme and podspec files --- README.md | 28 +++++++++++++++++++ SurfUtils.podspec | 5 ++++ .../ItemsScrollManager.swift | 1 - 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cf52076..4ead2ec 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ pod 'SurfUtils/$UTIL_NAME$', :git => "https://github.com/surfstudio/iOS-Utils.gi - [SettingsRouter](#settingsrouter) - позволяет выполнить переход в настройки приложения/устройства - [AdvancedNavigationStackManagement](#advancednavigationstackmanagement) - расширенная версия методов push/pop у UINavigationController - [WordDeclinationSelector](#worddeclinationselector) - позволяет получить нужное склонение слова +- [ItemsScrollManager](#itemsscrollmanager) - менеджер для поэлементного скролла карусели ## Утилиты @@ -122,6 +123,33 @@ navigationController?.pushViewController(controller, animated: true, completion: let correctForm = WordDeclinationSelector.declineWord(for: 6, from: WordDeclensions("день", "дня", "дней")) ``` +### ItemsScrollManager + +Утилита для так называемого "порционного скролла". +Очень часто в проекте необходимо реализовать так называемую "карусель", где представлены некоторые элементы, просматривать которые можно посредством горизонтального скролла. При этом очень часто требуется, чтобы после скролла такой карусели она автоматически подскралливалась к какому-либо элементу, а не застывала на полпути, обрезая элементы в карусели. +Данная утилита предназначена для того, чтобы в левой части экрана всегда находилось начало какого-либо элемента. + +Пример: +```Swift +// Создаем менеджер, указывая ширину ячейки карусели, расстояние между ячейками, а также отступы для секции UICollectionView с каруселью +scrollManager = ItemsScrollManager(cellWidth: 200, + cellOffset: 10, + insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)) + +// После чего необходимо добавить вызовы следующих методов в методы UIScrollViewDelegate +extension ViewController: UIScrollViewDelegate { + + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + scrollManager?.setTargetContentOffset(targetContentOffset, for: scrollView) + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + scrollManager?.setBeginDraggingOffset(scrollView.contentOffset.x) + } + +} +``` + ## Версионирование В качестве принципа версионирования используется [Семантическое версионирования (Semantic Versioning)](https://semver.org/). diff --git a/SurfUtils.podspec b/SurfUtils.podspec index 7c0666e..b519657 100644 --- a/SurfUtils.podspec +++ b/SurfUtils.podspec @@ -63,4 +63,9 @@ Pod::Spec.new do |s| sp.framework = 'Foundation' end + s.subspec 'ItemsScrollManager' do |sp| + sp.source_files = 'Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift' + sp.framework = 'UIKit' + end + end diff --git a/Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift b/Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift index 0272711..d10ebb8 100644 --- a/Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift +++ b/Utils/Utils/ItemsScrollManager/ItemsScrollManager.swift @@ -30,7 +30,6 @@ import UIKit /// } /// /// } -/// /// ``` public final class ItemsScrollManager { From 42dbcbcbec11036dd7dd4b1198411923c45e298f Mon Sep 17 00:00:00 2001 From: Alexander Chausov Date: Thu, 10 Jan 2019 11:08:43 +0300 Subject: [PATCH 13/13] small fixes by comments from PR, BlurBuilder implemented as extension for UIView --- README.md | 2 +- SurfUtils.podspec | 2 +- Utils/Utils.xcodeproj/project.pbxproj | 24 +++++++++---------- .../Utils/SettingsRouter/SettingsRouter.swift | 3 ++- .../UIView+BlurBuilder.swift} | 19 +++++++-------- .../WordDeclinationSelector.swift | 2 +- 6 files changed, 26 insertions(+), 26 deletions(-) rename Utils/Utils/{BlurBuilder/BlurBuilder.swift => UIView/UIView+BlurBuilder.swift} (54%) diff --git a/README.md b/README.md index 4ead2ec..c9ba3b5 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ let queryString = dict.toQueryString() Пример: ```Swift -BlurBuilder.addBlur(on: bluredView, with: UIColor.white.withAlphaComponent(0.1), style: .light) +bluredView.addBlur(color: UIColor.white.withAlphaComponent(0.1), style: .light) ``` ### RouteMeasurer diff --git a/SurfUtils.podspec b/SurfUtils.podspec index b519657..65f5a63 100644 --- a/SurfUtils.podspec +++ b/SurfUtils.podspec @@ -39,7 +39,7 @@ Pod::Spec.new do |s| end s.subspec 'BlurBuilder' do |sp| - sp.source_files = 'Utils/Utils/BlurBuilder/BlurBuilder.swift' + sp.source_files = 'Utils/Utils/UIView/UIView+BlurBuilder.swift' sp.framework = 'UIKit' end diff --git a/Utils/Utils.xcodeproj/project.pbxproj b/Utils/Utils.xcodeproj/project.pbxproj index bcf544e..cb49091 100644 --- a/Utils/Utils.xcodeproj/project.pbxproj +++ b/Utils/Utils.xcodeproj/project.pbxproj @@ -13,9 +13,9 @@ 4F3ED9FC211C27FD0030DD45 /* String+Attributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F3ED9FB211C27FD0030DD45 /* String+Attributes.swift */; }; 80437D26214045EF0095A8D0 /* JailbreakDetect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80437D25214045EF0095A8D0 /* JailbreakDetect.swift */; }; 902C67EC21E4D20B007B13CC /* ItemsScrollManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 902C67EB21E4D20B007B13CC /* ItemsScrollManager.swift */; }; + 902CA34121E7331E00396923 /* UIView+BlurBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 902CA34021E7331E00396923 /* UIView+BlurBuilder.swift */; }; 907F0FE321DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE221DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift */; }; 907F0FE621DCD3A0001CCB07 /* SettingsRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE521DCD3A0001CCB07 /* SettingsRouter.swift */; }; - 907F0FE921DCD5C5001CCB07 /* BlurBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */; }; 907F0FEC21DCD7E1001CCB07 /* RouteMeasurer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FEB21DCD7E1001CCB07 /* RouteMeasurer.swift */; }; 907F0FEF21DCDB43001CCB07 /* WordDeclinationSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907F0FEE21DCDB43001CCB07 /* WordDeclinationSelector.swift */; }; 90C101C821E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C101C721E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift */; }; @@ -46,9 +46,9 @@ 4F3ED9FB211C27FD0030DD45 /* String+Attributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Attributes.swift"; sourceTree = ""; }; 80437D25214045EF0095A8D0 /* JailbreakDetect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JailbreakDetect.swift; sourceTree = ""; }; 902C67EB21E4D20B007B13CC /* ItemsScrollManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsScrollManager.swift; sourceTree = ""; }; + 902CA34021E7331E00396923 /* UIView+BlurBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+BlurBuilder.swift"; sourceTree = ""; }; 907F0FE221DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+AdvancedNavigationStackManagement.swift"; sourceTree = ""; }; 907F0FE521DCD3A0001CCB07 /* SettingsRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRouter.swift; sourceTree = ""; }; - 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurBuilder.swift; sourceTree = ""; }; 907F0FEB21DCD7E1001CCB07 /* RouteMeasurer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteMeasurer.swift; sourceTree = ""; }; 907F0FEE21DCDB43001CCB07 /* WordDeclinationSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordDeclinationSelector.swift; sourceTree = ""; }; 90C101C721E0B1E0002E8E65 /* WordDeclinationSelectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordDeclinationSelectorTests.swift; sourceTree = ""; }; @@ -107,7 +107,6 @@ 4F3ED9E1211C27CF0030DD45 /* Utils */ = { isa = PBXGroup; children = ( - 907F0FE721DCD5B9001CCB07 /* BlurBuilder */, 18F2361221D214CC00169AC9 /* Dictionary */, 902C67EA21E4D1EF007B13CC /* ItemsScrollManager */, 80437D24214045B30095A8D0 /* JailbreakDetect */, @@ -115,6 +114,7 @@ 907F0FE421DCD375001CCB07 /* SettingsRouter */, 4F3ED9FA211C27E80030DD45 /* String */, 907F0FE121DCD1DB001CCB07 /* UINavigationController */, + 902CA33F21E732F700396923 /* UIView */, E9B06306214691F30080C391 /* VibrationFeedbackManager */, 907F0FED21DCDB1F001CCB07 /* WordDeclinationSelector */, 4F3ED9E2211C27CF0030DD45 /* Utils.h */, @@ -157,6 +157,14 @@ path = ItemsScrollManager; sourceTree = ""; }; + 902CA33F21E732F700396923 /* UIView */ = { + isa = PBXGroup; + children = ( + 902CA34021E7331E00396923 /* UIView+BlurBuilder.swift */, + ); + path = UIView; + sourceTree = ""; + }; 907F0FE121DCD1DB001CCB07 /* UINavigationController */ = { isa = PBXGroup; children = ( @@ -173,14 +181,6 @@ path = SettingsRouter; sourceTree = ""; }; - 907F0FE721DCD5B9001CCB07 /* BlurBuilder */ = { - isa = PBXGroup; - children = ( - 907F0FE821DCD5C5001CCB07 /* BlurBuilder.swift */, - ); - path = BlurBuilder; - sourceTree = ""; - }; 907F0FEA21DCD7C6001CCB07 /* RouteMeasurer */ = { isa = PBXGroup; children = ( @@ -320,7 +320,6 @@ 907F0FEC21DCD7E1001CCB07 /* RouteMeasurer.swift in Sources */, 18F2361421D2150200169AC9 /* Dictionary+QueryStringBuilder.swift in Sources */, E9B063102147AECC0080C391 /* UIDevice+feedbackType.swift in Sources */, - 907F0FE921DCD5C5001CCB07 /* BlurBuilder.swift in Sources */, 902C67EC21E4D20B007B13CC /* ItemsScrollManager.swift in Sources */, E9B0630A2146924A0080C391 /* VibrationFeedbackManager.swift in Sources */, 907F0FE621DCD3A0001CCB07 /* SettingsRouter.swift in Sources */, @@ -328,6 +327,7 @@ 80437D26214045EF0095A8D0 /* JailbreakDetect.swift in Sources */, E9B0630E214693160080C391 /* UIDevice+hasHapticFeedback.swift in Sources */, 4F3ED9FC211C27FD0030DD45 /* String+Attributes.swift in Sources */, + 902CA34121E7331E00396923 /* UIView+BlurBuilder.swift in Sources */, 907F0FEF21DCDB43001CCB07 /* WordDeclinationSelector.swift in Sources */, 907F0FE321DCD20C001CCB07 /* UINavigationController+AdvancedNavigationStackManagement.swift in Sources */, ); diff --git a/Utils/Utils/SettingsRouter/SettingsRouter.swift b/Utils/Utils/SettingsRouter/SettingsRouter.swift index f335633..d9ae93f 100644 --- a/Utils/Utils/SettingsRouter/SettingsRouter.swift +++ b/Utils/Utils/SettingsRouter/SettingsRouter.swift @@ -15,12 +15,13 @@ public final class SettingsRouter { private enum Constants { static let deviceSettingsUrl = "App-prefs:root=General" + static let appSettingsUrl = UIApplicationOpenSettingsURLString } // MARK: - Public Methods public static func openAppSettings() { - guard let url = URL(string: UIApplicationOpenSettingsURLString) else { + guard let url = URL(string: Constants.appSettingsUrl) else { return } openExpectedURL(url) diff --git a/Utils/Utils/BlurBuilder/BlurBuilder.swift b/Utils/Utils/UIView/UIView+BlurBuilder.swift similarity index 54% rename from Utils/Utils/BlurBuilder/BlurBuilder.swift rename to Utils/Utils/UIView/UIView+BlurBuilder.swift index c5aacca..ae06af8 100644 --- a/Utils/Utils/BlurBuilder/BlurBuilder.swift +++ b/Utils/Utils/UIView/UIView+BlurBuilder.swift @@ -1,36 +1,35 @@ // -// BlurBuilder.swift +// UIView+BlurBuilder.swift // Utils // -// Created by Александр Чаусов on 02/01/2019. +// Created by Александр Чаусов on 10/01/2019. // Copyright © 2019 Surf. All rights reserved. // import UIKit -public final class BlurBuilder { +extension UIView { /// Method allows you to add blur with custom color and style on needed view /// /// Example of usage: /// ``` - /// BlurBuilder.addBlur(on: bluredView, with: UIColor.white.withAlphaComponent(0.1), style: .light) + /// bluredView.addBlur(color: UIColor.white.withAlphaComponent(0.1), style: .light) /// ``` /// /// - Parameters: - /// - view: View to which the blur will be added /// - color: Color of the blur effect /// - style: Style of the blur effect, default value is .dark - public static func addBlur(on view: UIView, with color: UIColor, style: UIBlurEffect.Style = .dark) { - view.backgroundColor = UIColor.clear + public func addBlur(color: UIColor, style: UIBlurEffect.Style = .dark) { + self.backgroundColor = UIColor.clear let blurEffect = UIBlurEffect(style: style) let blurEffectView = UIVisualEffectView(effect: blurEffect) - blurEffectView.frame = view.bounds + blurEffectView.frame = self.bounds blurEffectView.contentView.backgroundColor = color blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - view.insertSubview(blurEffectView, at: 0) + self.insertSubview(blurEffectView, at: 0) } - + } diff --git a/Utils/Utils/WordDeclinationSelector/WordDeclinationSelector.swift b/Utils/Utils/WordDeclinationSelector/WordDeclinationSelector.swift index 9186f11..c91ea1f 100644 --- a/Utils/Utils/WordDeclinationSelector/WordDeclinationSelector.swift +++ b/Utils/Utils/WordDeclinationSelector/WordDeclinationSelector.swift @@ -46,7 +46,7 @@ public final class WordDeclinationSelector { return declensions.genetivePlural } switch (ending % 10) { - case (1): + case 1: return declensions.singularNominative case 2, 3, 4: return declensions.genetiveSingular