diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8c922eb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,55 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + +jobs: + test-ios: + name: Test iOS + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - name: Test + run: | + xcodebuild \ + -project XColor.xcodeproj \ + -scheme "XColor iOS" \ + -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPhone 16' \ + clean test + + test-tvos: + name: Test tvOS + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - name: Test + run: | + xcodebuild \ + -project XColor.xcodeproj \ + -scheme "XColor tvOS" \ + -destination 'platform=tvOS Simulator,name=Apple TV 4K (3rd generation)' \ + clean test + + test-macos: + name: Test macOS + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - name: Test + run: | + xcodebuild \ + -project XColor.xcodeproj \ + -scheme "XColor macOS" \ + -destination 'platform=macOS,arch=arm64' \ + clean test + + test-spm: + name: Test SPM + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - name: Build and Test + run: swift test diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..7a0809a --- /dev/null +++ b/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "XColor", + platforms: [ + .iOS(.v16), + .watchOS(.v9), + .tvOS(.v16), + .macOS(.v13), + .visionOS(.v1) + ], + products: [ + .library(name: "XColor", targets: ["XColor"]) + ], + targets: [ + .target( + name: "XColor", + path: "Source", + exclude: ["Supporting Files"] + ), + .testTarget( + name: "XColorTests", + dependencies: ["XColor"], + path: "XColorTests" + ) + ] +) diff --git a/Source/XColor.swift b/Source/XColor.swift index 5633bbd..f9a41a5 100644 --- a/Source/XColor.swift +++ b/Source/XColor.swift @@ -11,7 +11,7 @@ struct Components { let green: UInt8 let blue: UInt8 let alpha: Double - + init(red: UInt8, green: UInt8, blue: UInt8, alpha: Double = 1) { self.red = red self.green = green @@ -20,23 +20,15 @@ struct Components { } } -class XColor { - +struct XColor { + private let color: UInt32 - private var alpha: Double = 1 - + private let alpha: Double + init?(hexColor: String) { guard let color = XColor.validatedColor(hexString: hexColor) else { return nil } self.color = color - } - - init?(hexColor: Int) { - self.color = UInt32(hexColor) - } - - init?(hexColor: Int, alpha: Double) { - self.color = UInt32(hexColor) - self.alpha = alpha + self.alpha = 1.0 } init?(hexColor: String, alpha: Double) { @@ -44,57 +36,52 @@ class XColor { self.color = color self.alpha = alpha } - + + init?(hexColor: Int) { + guard let color = UInt32(exactly: hexColor) else { return nil } + self.color = color + self.alpha = 1.0 + } + + init?(hexColor: Int, alpha: Double) { + guard let color = UInt32(exactly: hexColor) else { return nil } + self.color = color + self.alpha = alpha + } + var components: Components? { - if (0x000 ... 0xFFF ~= color) { - let red = UInt8((color & 0xF00) >> 8) - let green = UInt8((color & 0x0F0) >> 4) - let blue = UInt8(color & 0x00F) - return Components(red: red, green: green, blue: blue, alpha: alpha) - } else if (0x000000 ... 0xFFFFFF ~= color) { + if 0x000 ... 0xFFF ~= color { + // 3-char shorthand: each nibble is doubled (e.g. #FA4 → #FFAA44) + let rn = UInt8((color & 0xF00) >> 8) + let gn = UInt8((color & 0x0F0) >> 4) + let bn = UInt8(color & 0x00F) + return Components( + red: (rn << 4) | rn, + green: (gn << 4) | gn, + blue: (bn << 4) | bn, + alpha: alpha + ) + } else if 0x000000 ... 0xFFFFFF ~= color { let red = UInt8((color & 0xFF0000) >> 16) let green = UInt8((color & 0x00FF00) >> 8) let blue = UInt8(color & 0x0000FF) return Components(red: red, green: green, blue: blue, alpha: alpha) - } else if (0x00000000 ... 0xFFFFFFFF ~= color) { + } else { + // 8-char RGBA: last byte is implicit alpha let red = UInt8((color & 0xFF000000) >> 24) let green = UInt8((color & 0x00FF0000) >> 16) let blue = UInt8((color & 0x0000FF00) >> 8) - let alpha = Double(UInt8(color & 0x000000FF)) / 255 - return Components(red: red, green: green, blue: blue, alpha: alpha) + let implicitAlpha = Double(color & 0x000000FF) / 255 + return Components(red: red, green: green, blue: blue, alpha: implicitAlpha) } - - return nil - } - - private static func validatedColor(hexString: String) -> UInt32? { - guard let str = XColor.sanitizedHexString(hexString: hexString) else { - return nil - } - - guard let color = UInt32(str, radix: 16) else { - return nil - } - - return color } - private static func sanitizedHexString(hexString: String) -> String? { - + private static func validatedColor(hexString: String) -> UInt32? { var str = hexString - - if (str.hasPrefix("#")) { + if str.hasPrefix("#") { str.remove(at: str.startIndex) } - - if (!valid(str)) { - return nil - } - - return str - } - - private static func valid(_ s: String) -> Bool { - return s.count == 3 || s.count == 6 || s.count == 8 + guard str.count == 3 || str.count == 6 || str.count == 8 else { return nil } + return UInt32(str, radix: 16) } } diff --git a/XColor.podspec b/XColor.podspec index 870f314..0decca7 100644 --- a/XColor.podspec +++ b/XColor.podspec @@ -1,18 +1,19 @@ Pod::Spec.new do |spec| spec.name = "XColor" - spec.version = "0.3" + spec.version = "0.4" spec.summary = "XColor is a color handling extension for UIColor written in Swift." spec.homepage = "https://github.com/jaumevn/XColor" spec.license = { :type => "MIT", :file => "LICENSE" } spec.source = { :git => "https://github.com/jaumevn/XColor.git", :tag => "#{spec.version}" } spec.author = { "Jaume Vinas Navas" => "jaumevn@icloud.com" } - spec.ios.deployment_target = '8.0' - spec.watchos.deployment_target = '2.0' - spec.tvos.deployment_target = '9.0' - spec.macos.deployment_target = '10.9' + spec.ios.deployment_target = '16.0' + spec.watchos.deployment_target = '9.0' + spec.tvos.deployment_target = '16.0' + spec.macos.deployment_target = '13.0' + spec.visionos.deployment_target = '1.0' - spec.swift_version = '5.0' + spec.swift_version = '5.9' spec.source_files = 'Source/*.swift' end diff --git a/XColor.xcodeproj/project.pbxproj b/XColor.xcodeproj/project.pbxproj index 58b8f41..56f7384 100644 --- a/XColor.xcodeproj/project.pbxproj +++ b/XColor.xcodeproj/project.pbxproj @@ -604,7 +604,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 13.0; PRODUCT_BUNDLE_IDENTIFIER = "com.jaumevn.XColor-macOS"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = macosx; @@ -631,7 +631,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 13.0; PRODUCT_BUNDLE_IDENTIFIER = "com.jaumevn.XColor-macOS"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = macosx; @@ -653,7 +653,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 13.0; PRODUCT_BUNDLE_IDENTIFIER = "com.jaumevn.XColor-macOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -674,7 +674,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 13.0; PRODUCT_BUNDLE_IDENTIFIER = "com.jaumevn.XColor-macOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -818,7 +818,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Source/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -848,7 +848,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Source/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -870,6 +870,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = XColorTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -889,6 +890,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = XColorTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -926,7 +928,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 2.0; + WATCHOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; @@ -955,7 +957,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 4; - WATCHOS_DEPLOYMENT_TARGET = 2.0; + WATCHOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; }; @@ -983,7 +985,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 16.0; }; name = Debug; }; @@ -1011,7 +1013,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 16.0; }; name = Release; }; @@ -1032,7 +1034,7 @@ SDKROOT = appletvos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 12.4; + TVOS_DEPLOYMENT_TARGET = 16.0; }; name = Debug; }; @@ -1053,7 +1055,7 @@ SDKROOT = appletvos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 12.4; + TVOS_DEPLOYMENT_TARGET = 16.0; }; name = Release; }; diff --git a/XColorTests/UIColorTests.swift b/XColorTests/UIColorTests.swift index f8a06ff..6c8d0ad 100644 --- a/XColorTests/UIColorTests.swift +++ b/XColorTests/UIColorTests.swift @@ -8,7 +8,10 @@ import XCTest -#if os(iOS) +#if SWIFT_PACKAGE +import Cocoa +@testable import XColor +#elseif os(iOS) import UIKit @testable import XColor_iOS #elseif os(tvOS) @@ -34,7 +37,7 @@ class UIColorTests: XCTestCase { XCTAssertEqual(components?.red, 0xAC / 255 , "Red component should be 0xAC") XCTAssertEqual(components?.green, 0x34 / 255 , "Green component should be 0x34") XCTAssertEqual(components?.blue, 0xCF / 255 , "Blue component should be 0xCF") - XCTAssertEqual(components?.alpha, 0xFF / 255 , "Alpha component should be 0xAC") + XCTAssertEqual(components?.alpha, 0xFF / 255 , "Alpha component should be 0xFF") } func testColorWithStringAndImplicitAlphaChannel() { @@ -69,9 +72,9 @@ class UIColorTests: XCTestCase { XCTAssertEqual(components?.red, 0xAC / 255 , "Red component should be 0xAC") XCTAssertEqual(components?.green, 0x34 / 255 , "Green component should be 0x34") XCTAssertEqual(components?.blue, 0xCF / 255 , "Blue component should be 0xCF") - XCTAssertEqual(components?.alpha, 0.3 , "Alpha component should be 0x88") + XCTAssertEqual(components?.alpha, 0.3 , "Alpha component should be 0.3") } - + func testColorWithNumber() { // GIVEN let hex = 0xAC34CF @@ -86,7 +89,7 @@ class UIColorTests: XCTestCase { XCTAssertEqual(components?.red, 0xAC / 255 , "Red component should be 0xAC") XCTAssertEqual(components?.green, 0x34 / 255 , "Green component should be 0x34") XCTAssertEqual(components?.blue, 0xCF / 255 , "Blue component should be 0xCF") - XCTAssertEqual(components?.alpha, 0xFF / 255 , "Alpha component should be 0x88") + XCTAssertEqual(components?.alpha, 0xFF / 255 , "Alpha component should be 0xFF") } func testColorWithNumberAndImplicitAlphaChannel() { @@ -121,9 +124,9 @@ class UIColorTests: XCTestCase { XCTAssertEqual(components?.red, 0xAC / 255 , "Red component should be 0xAC") XCTAssertEqual(components?.green, 0x34 / 255 , "Green component should be 0x34") XCTAssertEqual(components?.blue, 0xCF / 255 , "Blue component should be 0xCF") - XCTAssertEqual(components?.alpha, 0.3 , "Alpha component should be 0x88") + XCTAssertEqual(components?.alpha, 0.3 , "Alpha component should be 0.3") } - + func testColorWithInvalidString() { // GIVEN let hex = ".This is an invalid string!" diff --git a/XColorTests/XColorTests.swift b/XColorTests/XColorTests.swift index 5804a6b..0b8ec0a 100644 --- a/XColorTests/XColorTests.swift +++ b/XColorTests/XColorTests.swift @@ -9,7 +9,9 @@ import XCTest import CoreGraphics -#if os(iOS) +#if SWIFT_PACKAGE +@testable import XColor +#elseif os(iOS) @testable import XColor_iOS #elseif os(tvOS) @testable import XColor_tvOS @@ -38,9 +40,9 @@ class XColorTests: XCTestCase { let color = XColor(hexColor: hexString) // Expect let components = color?.components - XCTAssertEqual(components?.red, 0xF) - XCTAssertEqual(components?.green, 0xA) - XCTAssertEqual(components?.blue, 0x4) + XCTAssertEqual(components?.red, 0xFF) + XCTAssertEqual(components?.green, 0xAA) + XCTAssertEqual(components?.blue, 0x44) XCTAssertEqual(components?.alpha, 1.0) } @@ -118,9 +120,9 @@ class XColorTests: XCTestCase { let color = XColor(hexColor: hexNumber) // Expect let components = color?.components - XCTAssertEqual(components?.red, 0xF) - XCTAssertEqual(components?.green, 0xA) - XCTAssertEqual(components?.blue, 0x4) + XCTAssertEqual(components?.red, 0xFF) + XCTAssertEqual(components?.green, 0xAA) + XCTAssertEqual(components?.blue, 0x44) XCTAssertEqual(components?.alpha, 1.0) }