diff --git a/.travis.yml b/.travis.yml index 36f914d..bf044f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ matrix: - osx_image: xcode10 env: SCHEME="BXSwiftUtils" DESTINATION="platform=macOS,arch=x86_64" - osx_image: xcode10 - env: SCHEME="BXSwiftUtilsx" DESTINATION="platform=iOS Simulator,name=iPhone X,OS=12.0" + env: SCHEME="BXSwiftUtils" DESTINATION="platform=iOS Simulator,name=iPhone X,OS=12.0" - osx_image: xcode10 env: CREATE_RELEASE="true" script: sh deploy.sh diff --git a/BXSwiftUtils.xcodeproj/project.pbxproj b/BXSwiftUtils.xcodeproj/project.pbxproj index 2b837e1..9066d42 100644 --- a/BXSwiftUtils.xcodeproj/project.pbxproj +++ b/BXSwiftUtils.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 48C248142088CF4100DC9317 /* NSAttributedString+CodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48C248132088CF4100DC9317 /* NSAttributedString+CodableTests.swift */; }; 48C3DF6620E10DC300359288 /* TypedKVO+propagateChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48C3DF6420E10DC300359288 /* TypedKVO+propagateChanges.swift */; }; 48C3DF6820E10E2400359288 /* TypedKVO+propagateChangesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48C3DF6720E10E2400359288 /* TypedKVO+propagateChangesTests.swift */; }; + 48D9E36121A6B75E0057B930 /* Comparable+ClipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48D9E36021A6B75E0057B930 /* Comparable+ClipTests.swift */; }; 48DA8193206A4E9C009D1E6C /* KVOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DA8192206A4E9C009D1E6C /* KVOTests.swift */; }; 48DA8199206A50DB009D1E6C /* NSException+ToSwiftError.h in Headers */ = {isa = PBXBuildFile; fileRef = 48DA8196206A50DB009D1E6C /* NSException+ToSwiftError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 48DA819B206A50DB009D1E6C /* NSException+ToSwiftError.m in Sources */ = {isa = PBXBuildFile; fileRef = 48DA8197206A50DB009D1E6C /* NSException+ToSwiftError.m */; }; @@ -142,6 +143,7 @@ 48C248132088CF4100DC9317 /* NSAttributedString+CodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+CodableTests.swift"; sourceTree = ""; }; 48C3DF6420E10DC300359288 /* TypedKVO+propagateChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TypedKVO+propagateChanges.swift"; sourceTree = ""; }; 48C3DF6720E10E2400359288 /* TypedKVO+propagateChangesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TypedKVO+propagateChangesTests.swift"; sourceTree = ""; }; + 48D9E36021A6B75E0057B930 /* Comparable+ClipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Comparable+ClipTests.swift"; sourceTree = ""; }; 48DA8192206A4E9C009D1E6C /* KVOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KVOTests.swift; sourceTree = ""; }; 48DA8196206A50DB009D1E6C /* NSException+ToSwiftError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSException+ToSwiftError.h"; sourceTree = ""; }; 48DA8197206A50DB009D1E6C /* NSException+ToSwiftError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSException+ToSwiftError.m"; sourceTree = ""; }; @@ -290,6 +292,7 @@ D06CC9212178C096009C2991 /* Files */, 48C248122088CF1C00DC9317 /* Strings */, D06CC9292178D22A009C2991 /* Data */, + 48D9E35F21A6B73D0057B930 /* Math & Geometry */, 4875F24C2051EC75009985EC /* Collection */, 489E56A72052CAB30071BBF1 /* Logging */, 4853E3882056BDB500938B82 /* Threading */, @@ -397,6 +400,14 @@ path = Strings; sourceTree = ""; }; + 48D9E35F21A6B73D0057B930 /* Math & Geometry */ = { + isa = PBXGroup; + children = ( + 48D9E36021A6B75E0057B930 /* Comparable+ClipTests.swift */, + ); + path = "Math & Geometry"; + sourceTree = ""; + }; 48DA8195206A509A009D1E6C /* Exceptions */ = { isa = PBXGroup; children = ( @@ -695,6 +706,7 @@ D06CC91F2178B089009C2991 /* String+RegexTests.swift in Sources */, D06CC9272178D209009C2991 /* Data+MutationTests.swift in Sources */, 48DE068D20876E52000246CD /* Dictionary+EnumKeysTests.swift in Sources */, + 48D9E36121A6B75E0057B930 /* Comparable+ClipTests.swift in Sources */, 4853E38A2056BDE400938B82 /* SynchronizedTests.swift in Sources */, 4826E581208F534000A5BA9B /* Array+DecodableTests.swift in Sources */, 48FF9BCA20E1174C00024C10 /* Weak.swift in Sources */, @@ -906,7 +918,8 @@ CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = BXSwiftUtilsTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.boinx.$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -921,7 +934,8 @@ CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = BXSwiftUtilsTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.boinx.$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; @@ -930,6 +944,7 @@ 4875F271205299A6009985EC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -939,7 +954,6 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = com.boinx.BXSwiftUtils; PRODUCT_NAME = BXSwiftUtils; SKIP_INSTALL = YES; @@ -960,6 +974,7 @@ 4875F272205299A6009985EC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -969,7 +984,6 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = com.boinx.BXSwiftUtils; PRODUCT_NAME = BXSwiftUtils; SKIP_INSTALL = YES; diff --git a/BXSwiftUtils.xcodeproj/xcshareddata/xcschemes/BXSwiftUtils.xcscheme b/BXSwiftUtils.xcodeproj/xcshareddata/xcschemes/BXSwiftUtils.xcscheme index e8ac883..d34163a 100644 --- a/BXSwiftUtils.xcodeproj/xcshareddata/xcschemes/BXSwiftUtils.xcscheme +++ b/BXSwiftUtils.xcodeproj/xcshareddata/xcschemes/BXSwiftUtils.xcscheme @@ -28,7 +28,26 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + + diff --git a/BXSwiftUtils/Math & Geometry/Comparable+Clip.swift b/BXSwiftUtils/Math & Geometry/Comparable+Clip.swift index 4adc5a3..2a741e9 100644 --- a/BXSwiftUtils/Math & Geometry/Comparable+Clip.swift +++ b/BXSwiftUtils/Math & Geometry/Comparable+Clip.swift @@ -10,24 +10,63 @@ import Foundation extension Comparable { - /// Returns `self` clipped to the closed interval `[minValue, maxValue]`. - /// - /// - Parameters: - /// - minValue: The minimum allowed value. Will clip `self` if smaller. - /// - maxValue: The maximum allowed value. Will clip `self` if greater. - /// - Returns: `self` if `self` is within the interval `[minValue, maxValue]`, `minValue` or `maxValue` otherwise. - public func clipped(minValue: T, maxValue: T) -> T where T : Comparable + /** + Clips a value into the closed range `[min, max]` without modifying the original value. + + - Parameter minValue: The minimum allowed value. + - Parameter maxValue: The maximum allowed value. + - Returns: A new value that lies within the closed range `[min, max]`. + */ + public func clipped(min minValue: Self, max maxValue: Self) -> Self + { + var copy = self + copy.clip(min: minValue, max: maxValue) + return copy + } + + /** + Clips a value into the closed range `[min, max]` by modifying the original value. + + - Parameter minValue: The minimum allowed value. + - Parameter maxValue: The maximum allowed value. + */ + public mutating func clip(min minValue: Self, max maxValue: Self) { - return min(maxValue, max(minValue, self as! T)) + if minValue > maxValue + { + NSException.raise(.invalidArgumentException, format: "Can't clip value \(self) with maxValue \(maxValue) being larger than minValue \(minValue)", arguments: getVaList([])) + } + + if self < minValue + { + self = minValue + } + else if self > maxValue + { + self = maxValue + } } - /// Clips to the closed interval `[minValue, maxValue]`. - /// - /// - Parameters: - /// - minValue: The minimum allowed value. Will clip `self` if smaller. - /// - maxValue: The maximum allowed value. Will clip `self` if greater. - public mutating func clip(minValue: T, maxValue: T) where T : Comparable + /** + Clips a value into the given closed range without modifying the original value. + + - Parameter range: Closed range that conveys the minimum and maximum allowed value. + - Returns: New value that lies within the closed range `range`. + */ + public func clipped(to range: ClosedRange) -> Self + { + var copy = self + copy.clip(min: range.lowerBound, max: range.upperBound) + return copy + } + + /** + Clips a value into the given closed range by modifying the original value. + + - Parameter range: Closed range that conveys the minimum and maximum allowed value. + */ + public mutating func clip(to range: ClosedRange) { - self = min(maxValue, max(minValue, self as! T)) as! Self + self.clip(min: range.lowerBound, max: range.upperBound) } } diff --git a/BXSwiftUtilsTests/Math & Geometry/Comparable+ClipTests.swift b/BXSwiftUtilsTests/Math & Geometry/Comparable+ClipTests.swift new file mode 100644 index 0000000..56fcc1b --- /dev/null +++ b/BXSwiftUtilsTests/Math & Geometry/Comparable+ClipTests.swift @@ -0,0 +1,56 @@ +// +// Comparable+ClipTests.swift +// BXSwiftUtils-Tests +// +// Created by Stefan Fochler on 22.11.18. +// Copyright © 2018 Boinx Software Ltd. & Imagine GbR. All rights reserved. +// + +import XCTest +import BXSwiftUtils + +class Comparable_ClipTests : XCTestCase +{ + func testClippedMinMax() + { + XCTAssertEqual(5.0.clipped(min: 0.0, max: 10.0), 5.0) + XCTAssertEqual(5.0.clipped(min: 0.0, max: 5.0), 5.0) + XCTAssertEqual(5.0.clipped(min: 0.0, max: 4.0), 4.0) + XCTAssertEqual(5.0.clipped(min: 6.0, max: 10.0), 6.0) + } + + func testClippedRange() + { + XCTAssertEqual(5.0.clipped(to: 0...10), 5.0) + XCTAssertEqual(5.0.clipped(to: 0...5), 5.0) + XCTAssertEqual(5.0.clipped(to: 0...4), 4.0) + XCTAssertEqual(5.0.clipped(to: 6...10), 6.0) + } + + func testClipMinMax() + { + var value = 5.0 + value.clip(min: 2.0, max: 3.0) + XCTAssertEqual(value, 3.0) + } + + func testClipRange() + { + var value = 5.0 + value.clip(to: 2...3) + XCTAssertEqual(value, 3.0) + } + + func testInvalidMinMax() + { + XCTAssertThrowsError(try NSException.toSwiftError + { + let _ = 5.0.clipped(min: 10.0, max: 0) + }) + XCTAssertThrowsError(try NSException.toSwiftError + { + var value = 5.0 + value.clip(min: 10.0, max: 0) + }) + } +} diff --git a/deploy.sh b/deploy.sh index 796a779..bbf3b83 100644 --- a/deploy.sh +++ b/deploy.sh @@ -6,7 +6,7 @@ _artifacts="Artifacts" rm -Rf "$_artifacts" mkdir "$_artifacts" -set -o pipefail; xcodebuild -scheme "BXSwiftUtils-macOS" -configuration "Release" clean build | xcpretty +set -o pipefail; xcodebuild -scheme "BXSwiftUtils" -configuration "Release" clean build | xcpretty source .bx_build_env @@ -14,7 +14,7 @@ pushd "$BUILD_PRODUCTS_DIR" zip -ryq "$SRCROOT/$_artifacts/BXSwiftUtils-macOS.framework.zip" "$PRODUCT_NAME" popd -set -o pipefail; xcodebuild -scheme "BXSwiftUtils-iOS" -configuration "Release" -destination "platform=iOS Simulator,name=iPhone X,OS=latest" clean build | xcpretty +set -o pipefail; xcodebuild -scheme "BXSwiftUtils" -configuration "Release" -destination "platform=iOS Simulator,name=iPhone X,OS=latest" clean build | xcpretty source .bx_build_env