From e14cb236086c6632d17ce13c9d794d143149bc4b Mon Sep 17 00:00:00 2001 From: Dicky Darmawan Date: Wed, 10 Jun 2026 10:53:10 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20iOS=20best-practice=20audit=20?= =?UTF-8?q?=E2=80=94=20unified=20logging,=20iOS=2015=20baseline,=20API=20c?= =?UTF-8?q?leanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace all print() with os.Logger via an internal Log façade (subsystem com.screenutil; core/debug/benchmark categories) - Raise deployment baseline to iOS 15 / macOS 12 / tvOS 15 / watchOS 8 and drop now-redundant @available annotations in SwiftUI - Remove dead splitScreenMode from the config and ScreenUtilConfigurable - Gate measurePerformance / benchmarkScalingOperations behind #if DEBUG - Mark ScreenScalable / ScreenUtilConfigurable / ScreenDimensionProvider Sendable - Derive ScreenDimensions.current from the active UIWindowScene instead of the soft-deprecated UIScreen.main - Conform ScalingLimits, ScreenMetrics, ScreenDimensions, SafeAreaInsets, ScreenUtilConfiguration, ScaleType, and DeviceType to Equatable/Hashable - Unify ScaledValue's nested ScaleType into the top-level ScaleType (.font → .text, .auto removed, default .width) - Add doc comments across the public API; update README and CHANGELOG --- CHANGELOG.md | 7 +++ Package.swift | 8 +-- README.md | 6 +- Sources/ScreenUtil/Core/ScaleType.swift | 27 +++++++-- Sources/ScreenUtil/Core/ScalingLimits.swift | 10 +++- Sources/ScreenUtil/Core/ScreenMetrics.swift | 28 ++++++++- Sources/ScreenUtil/Core/ScreenUtil.swift | 4 +- .../Core/ScreenUtilConfiguration.swift | 18 ++++-- .../ScreenUtil/Debug/ScreenUtilDebug.swift | 60 ++++++++++--------- Sources/ScreenUtil/Internal/Log.swift | 44 ++++++++++++++ Sources/ScreenUtil/Metrics/DeviceType.swift | 2 +- .../ScreenUtil/Metrics/SafeAreaInsets.swift | 10 +++- .../ScreenUtil/Metrics/ScreenDimensions.swift | 8 ++- Sources/ScreenUtil/Scaling/BatchScaling.swift | 17 ++++++ Sources/ScreenUtil/Scaling/FastScale.swift | 10 ++++ .../ScreenUtil/Scaling/Numeric+Scaling.swift | 36 +++++++++++ Sources/ScreenUtil/SwiftUI/ScaledValue.swift | 26 ++------ .../ScreenUtil/SwiftUI/View+Responsive.swift | 5 -- .../Core/EquatableConformanceTests.swift | 51 ++++++++++++++++ .../Core/ScreenUtilTests.swift | 2 - .../SwiftUI/PropertyWrapperTests.swift | 7 +-- 21 files changed, 301 insertions(+), 85 deletions(-) create mode 100644 Sources/ScreenUtil/Internal/Log.swift create mode 100644 Tests/ScreenUtilTests/Core/EquatableConformanceTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 5505084..cd927fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Restructured `Sources` into a platform-isolated layout (Core / Internal / Metrics / Scaling / UIKit / SwiftUI / Debug). - SwiftUI `responsiveCornerRadius` now uses `clipShape` (deprecated `.cornerRadius` removed). +- Replaced all `print` logging with unified logging (`os.Logger`, subsystem `com.screenutil`). +- Raised deployment baseline to iOS 15 / macOS 12 / tvOS 15 / watchOS 8. +- `ScreenDimensions.current` now derives from the active `UIWindowScene` instead of the soft-deprecated `UIScreen.main`. +- Public value types (`ScalingLimits`, `ScreenMetrics`, `ScreenDimensions`, `SafeAreaInsets`, `ScreenUtilConfiguration`, `ScaleType`, `DeviceType`) now conform to `Equatable`/`Hashable`. ### Fixed - Scale-factor data race on concurrent reads during reconfigure. @@ -29,6 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ScaleType` cases `.font`, `.min`, `.max`, `.auto` (now `.width`, `.height`, `.text`, `.radius`). - `ScreenUtilConfiguration.deviceType` — use `ScreenUtil.shared.deviceType`. - Dead SwiftUI modifiers/wrappers, duplicate types, and the buggy `Array` scaling extension. +- `ScreenUtilConfiguration.splitScreenMode` — unused, removed from the config and `ScreenUtilConfigurable`. +- `ScaledValue.ScaleType` nested enum — `@ScaledValue` now uses the top-level `ScaleType` (`.font` → `.text`, `.auto` removed; default is `.width`). +- Benchmark APIs (`measurePerformance`, `benchmarkScalingOperations`) are now `#if DEBUG`-only. ### Dependencies - Added [apple/swift-atomics](https://github.com/apple/swift-atomics) for the lock-free snapshot. diff --git a/Package.swift b/Package.swift index 6b49841..518ef62 100644 --- a/Package.swift +++ b/Package.swift @@ -4,10 +4,10 @@ import PackageDescription let package = Package( name: "ScreenUtil", platforms: [ - .iOS(.v13), - .macOS(.v10_15), - .tvOS(.v13), - .watchOS(.v6) + .iOS(.v15), + .macOS(.v12), + .tvOS(.v15), + .watchOS(.v8) ], products: [ .library( diff --git a/README.md b/README.md index 9b763af..428cc1e 100644 --- a/README.md +++ b/README.md @@ -259,12 +259,12 @@ var batchScaler: BatchScaler ```swift ScreenUtil.shared.debug.printCurrentConfiguration() -ScreenUtil.shared.debug.benchmarkScalingOperations() _ = ScreenUtil.shared.debug.validateScaling() let report = ScreenUtil.shared.debug.generateTestReport() #if DEBUG -ScreenUtil.shared.debug.showDebugOverlay(on: view) // UIKit +ScreenUtil.shared.debug.benchmarkScalingOperations() // DEBUG builds only +ScreenUtil.shared.debug.showDebugOverlay(on: view) // UIKit #endif ``` @@ -278,7 +278,7 @@ swift test --sanitize=thread # data-race check ## 📱 Device Support -iPhone, iPad (incl. Split View), plus macOS / tvOS / watchOS builds. iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+. +iPhone, iPad (incl. Split View), plus macOS / tvOS / watchOS builds. iOS 15.0+ / macOS 12.0+ / tvOS 15.0+ / watchOS 8.0+. ## 🤝 Contributing diff --git a/Sources/ScreenUtil/Core/ScaleType.swift b/Sources/ScreenUtil/Core/ScaleType.swift index 25c6e90..73a15c4 100644 --- a/Sources/ScreenUtil/Core/ScaleType.swift +++ b/Sources/ScreenUtil/Core/ScaleType.swift @@ -9,29 +9,46 @@ import Foundation import CoreGraphics -public protocol ScreenUtilConfigurable { +/// Describes a type that holds the core ScreenUtil configuration values. +public protocol ScreenUtilConfigurable: Sendable { + /// The reference design canvas size (e.g. 375×812 from Figma). var designSize: CGSize { get set } + /// When `true`, text scaling uses the minimum of the width and height factors to prevent oversized text. var minTextAdapt: Bool { get set } - var splitScreenMode: Bool { get set } + /// The min/max bounds applied to all computed scale factors. var scalingLimits: ScalingLimits { get set } } -public protocol ScreenScalable { +/// Describes a type that can scale a value using a given `ScaleType`. +public protocol ScreenScalable: Sendable { + /// Returns the scaled value for `value` using the specified `scaleType`. func scale(for value: CGFloat, scaleType: ScaleType) -> CGFloat + /// Returns a fast (cached-factor) scaled value for `value` using the specified `scaleType`. func fastScale(for value: CGFloat, scaleType: ScaleType) -> CGFloat } -public protocol ScreenDimensionProvider { +/// Describes a type that exposes current device screen dimensions and inset metrics. +public protocol ScreenDimensionProvider: Sendable { + /// The logical screen width in points. var screenWidth: CGFloat { get } + /// The logical screen height in points. var screenHeight: CGFloat { get } + /// The safe-area inset at the top of the screen in points. var safeAreaTop: CGFloat { get } + /// The safe-area inset at the bottom of the screen in points. var safeAreaBottom: CGFloat { get } + /// The status bar height in points. var statusBarHeight: CGFloat { get } } -public enum ScaleType: Sendable { +/// The dimension axis a value is scaled against. +public enum ScaleType: Sendable, Equatable, Hashable { + /// Scale by the width factor (design width → device width). case width + /// Scale by the height factor (design height → device height). case height + /// Scale by the text factor (min of width/height when `minTextAdapt` is enabled). case text + /// Scale by the smaller of the width and height factors, suitable for corner radii. case radius } diff --git a/Sources/ScreenUtil/Core/ScalingLimits.swift b/Sources/ScreenUtil/Core/ScalingLimits.swift index 3d6c964..bd4c5bc 100644 --- a/Sources/ScreenUtil/Core/ScalingLimits.swift +++ b/Sources/ScreenUtil/Core/ScalingLimits.swift @@ -9,22 +9,30 @@ import Foundation import CoreGraphics -public struct ScalingLimits: Sendable { +/// Defines the minimum and maximum bounds that are clamped onto computed scale factors. +public struct ScalingLimits: Sendable, Equatable, Hashable { + /// The lower bound for any scale factor; clamped to a minimum of 0.1 at initialisation. public let minScale: CGFloat + /// The upper bound for any scale factor; clamped to a maximum of 5.0 at initialisation. public let maxScale: CGFloat + /// Creates a `ScalingLimits` with the given min/max bounds (clamped to the absolute safe range 0.1–5.0). public init(minScale: CGFloat = 0.5, maxScale: CGFloat = 2.0) { self.minScale = max(0.1, minScale) self.maxScale = min(5.0, maxScale) } + /// Returns `value` clamped to `[minScale, maxScale]`. public func clamp(_ value: CGFloat) -> CGFloat { return min(maxScale, max(minScale, value)) } + /// Default limits: 0.5–2.0, suitable for most iPhone designs. public static let `default` = ScalingLimits() + /// Strict limits: 0.8–1.25, recommended for iPad and large-canvas designs. public static let strict = ScalingLimits(minScale: 0.8, maxScale: 1.25) + /// Relaxed limits: 0.3–3.0, allowing wider scaling for experimental or TV layouts. public static let relaxed = ScalingLimits(minScale: 0.3, maxScale: 3.0) } diff --git a/Sources/ScreenUtil/Core/ScreenMetrics.swift b/Sources/ScreenUtil/Core/ScreenMetrics.swift index cc071b0..7178b80 100644 --- a/Sources/ScreenUtil/Core/ScreenMetrics.swift +++ b/Sources/ScreenUtil/Core/ScreenMetrics.swift @@ -9,13 +9,20 @@ import Foundation import CoreGraphics -public struct ScreenMetrics: Sendable { +/// A point-in-time snapshot of the device screen metrics used for scaling. +public struct ScreenMetrics: Sendable, Equatable, Hashable { + /// Logical screen width in points. public let width: CGFloat + /// Logical screen height in points. public let height: CGFloat + /// Native pixel density scale factor (e.g. 2.0 for @2x displays). public let scale: CGFloat + /// Safe-area insets (top, bottom, left, right) in points. public let safeAreaInsets: (top: CGFloat, bottom: CGFloat, left: CGFloat, right: CGFloat) + /// Status bar height in points. public let statusBarHeight: CGFloat + /// Creates a `ScreenMetrics` snapshot with the given screen dimensions and insets. public init( width: CGFloat, height: CGFloat, @@ -29,4 +36,23 @@ public struct ScreenMetrics: Sendable { self.safeAreaInsets = safeAreaInsets self.statusBarHeight = statusBarHeight } + + public static func == (lhs: ScreenMetrics, rhs: ScreenMetrics) -> Bool { + lhs.width == rhs.width && + lhs.height == rhs.height && + lhs.scale == rhs.scale && + lhs.safeAreaInsets == rhs.safeAreaInsets && + lhs.statusBarHeight == rhs.statusBarHeight + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(width) + hasher.combine(height) + hasher.combine(scale) + hasher.combine(safeAreaInsets.top) + hasher.combine(safeAreaInsets.bottom) + hasher.combine(safeAreaInsets.left) + hasher.combine(safeAreaInsets.right) + hasher.combine(statusBarHeight) + } } diff --git a/Sources/ScreenUtil/Core/ScreenUtil.swift b/Sources/ScreenUtil/Core/ScreenUtil.swift index 35bb97c..104ed94 100644 --- a/Sources/ScreenUtil/Core/ScreenUtil.swift +++ b/Sources/ScreenUtil/Core/ScreenUtil.swift @@ -113,9 +113,7 @@ public final class ScreenUtil: ScreenScalable, ScreenDimensionProvider, Sendable @inline(__always) public func scale(for value: CGFloat, scaleType: ScaleType) -> CGFloat { guard value.isFinite else { - #if DEBUG - print("⚠️ ScreenUtil: Invalid input value \(value) for scale type \(scaleType)") - #endif + Log(.core, "Invalid input value \(value) for scale type \(scaleType)", level: .warning) return 0 } return value * scaleFactor(for: scaleType) diff --git a/Sources/ScreenUtil/Core/ScreenUtilConfiguration.swift b/Sources/ScreenUtil/Core/ScreenUtilConfiguration.swift index 3d620ae..5ea4fc6 100644 --- a/Sources/ScreenUtil/Core/ScreenUtilConfiguration.swift +++ b/Sources/ScreenUtil/Core/ScreenUtilConfiguration.swift @@ -9,54 +9,64 @@ import Foundation import CoreGraphics -public struct ScreenUtilConfiguration: ScreenUtilConfigurable, Sendable { +/// Configuration for the ScreenUtil scaling engine, specifying the design canvas and scaling behaviour. +public struct ScreenUtilConfiguration: ScreenUtilConfigurable, Sendable, Equatable, Hashable { + /// The reference design canvas size used to compute scale factors. public var designSize: CGSize + /// When `true`, font scaling uses the minimum of width/height factors to prevent text from becoming too large. public var minTextAdapt: Bool - public var splitScreenMode: Bool + /// The min/max bounds clamped onto all computed scale factors. public var scalingLimits: ScalingLimits + /// Creates a configuration with the given design size, text adaptation flag, and scaling limits. public init( designSize: CGSize = CGSize(width: 375, height: 812), minTextAdapt: Bool = true, - splitScreenMode: Bool = true, scalingLimits: ScalingLimits = .default ) { self.designSize = designSize self.minTextAdapt = minTextAdapt - self.splitScreenMode = splitScreenMode self.scalingLimits = scalingLimits } + /// Preset for designs targeting the iPhone 13 Pro (390×844 pt). public static let iPhone13Pro = ScreenUtilConfiguration( designSize: CGSize(width: 390, height: 844) ) + /// Preset for designs targeting the iPhone 14 Pro (393×852 pt). public static let iPhone14Pro = ScreenUtilConfiguration( designSize: CGSize(width: 393, height: 852) ) + /// Preset for designs targeting the iPhone 15 Pro (393×852 pt). public static let iPhone15Pro = ScreenUtilConfiguration( designSize: CGSize(width: 393, height: 852) ) + /// Preset for designs targeting the iPhone X / 11 Pro / 12 mini (375×812 pt). public static let iPhoneX = ScreenUtilConfiguration( designSize: CGSize(width: 375, height: 812) ) + /// Preset for designs targeting the iPhone 8 / SE 2nd gen (375×667 pt). public static let iPhone8 = ScreenUtilConfiguration( designSize: CGSize(width: 375, height: 667) ) + /// Preset for designs targeting the 11-inch iPad Pro (834×1194 pt) with strict scaling limits. public static let iPadPro11 = ScreenUtilConfiguration( designSize: CGSize(width: 834, height: 1194), scalingLimits: .strict ) + /// Preset for designs targeting the 12.9-inch iPad Pro (1024×1366 pt) with strict scaling limits. public static let iPadPro12_9 = ScreenUtilConfiguration( designSize: CGSize(width: 1024, height: 1366), scalingLimits: .strict ) + /// Returns `true` when `designSize` has positive, in-range dimensions (width ≤ 2000, height ≤ 3000). public func isValidDesignSize() -> Bool { return designSize.width > 0 && designSize.height > 0 && designSize.width <= 2000 && designSize.height <= 3000 diff --git a/Sources/ScreenUtil/Debug/ScreenUtilDebug.swift b/Sources/ScreenUtil/Debug/ScreenUtilDebug.swift index 199b3fe..0d684b1 100644 --- a/Sources/ScreenUtil/Debug/ScreenUtilDebug.swift +++ b/Sources/ScreenUtil/Debug/ScreenUtilDebug.swift @@ -12,34 +12,34 @@ import CoreGraphics import UIKit #endif +/// Debug and development utilities for inspecting ScreenUtil's active configuration and scaling behaviour. public struct ScreenUtilDebug: Sendable { + /// Shared singleton instance. public static let shared = ScreenUtilDebug() private init() {} + /// Logs the current screen metrics and scale factors via `os.Logger`. public func printCurrentConfiguration() { let screenUtil = ScreenUtil.shared let metrics = screenUtil.getScreenMetrics() - print(""" - ╔══════════════════════════════════════════════════════════╗ - ║ ScreenUtil Debug Info ║ - ╠══════════════════════════════════════════════════════════╣ - ║ Screen Dimensions: \(metrics.width) x \(metrics.height) pts ║ - ║ Scale Factor: \(metrics.scale)x ║ - ║ Safe Area: T:\(metrics.safeAreaInsets.0) B:\(metrics.safeAreaInsets.1) L:\(metrics.safeAreaInsets.2) R:\(metrics.safeAreaInsets.3) ║ - ║ Status Bar Height: \(metrics.statusBarHeight) pts ║ - ║ ║ - ║ Scale Factors: ║ - ║ Width Scale: \(screenUtil.scaleWidth) ║ - ║ Height Scale: \(screenUtil.scaleHeight) ║ - ║ Text Scale: \(screenUtil.scaleText) ║ - ║ ║ - ║ Device Type: \(screenUtil.deviceType) ║ - ╚══════════════════════════════════════════════════════════╝ - """) + let message = """ + ScreenUtil Debug Info + Screen Dimensions: \(metrics.width) x \(metrics.height) pts + Scale Factor: \(metrics.scale)x + Safe Area: T:\(metrics.safeAreaInsets.0) B:\(metrics.safeAreaInsets.1) L:\(metrics.safeAreaInsets.2) R:\(metrics.safeAreaInsets.3) + Status Bar Height: \(metrics.statusBarHeight) pts + Width Scale: \(screenUtil.scaleWidth) + Height Scale: \(screenUtil.scaleHeight) + Text Scale: \(screenUtil.scaleText) + Device Type: \(screenUtil.deviceType) + """ + Log(.debug, message) } + #if DEBUG + /// Runs `operation` `iterations` times and returns the last result alongside the average execution time. public func measurePerformance(_ operation: () -> T, iterations: Int = 10000) -> (result: T, averageTime: TimeInterval) { var totalTime: TimeInterval = 0 var result: T! @@ -55,12 +55,12 @@ public struct ScreenUtilDebug: Sendable { return (result, averageTime) } + /// Benchmarks standard, fast, and batch scaling paths and logs the results. public func benchmarkScalingOperations() { let testValue: CGFloat = 100.0 let iterations = 100000 - print("\n🚀 ScreenUtil Performance Benchmark") - print("Testing \(iterations) iterations...\n") + Log(.benchmark, "ScreenUtil Performance Benchmark — \(iterations) iterations") let standardW = measurePerformance({ testValue.w @@ -74,15 +74,16 @@ public struct ScreenUtilDebug: Sendable { ScreenUtil.shared.batchWidths([10, 20, 30, 40, 50]) }, iterations: iterations / 10) - print("📊 Results:") - print(" Standard .w: \(String(format: "%.2f", standardW.averageTime * 1_000_000)) μs") - print(" Fast .fastW: \(String(format: "%.2f", fastW.averageTime * 1_000_000)) μs") - print(" Batch operation: \(String(format: "%.2f", batchOperation.averageTime * 1_000_000)) μs") + Log(.benchmark, "Standard .w: \(String(format: "%.2f", standardW.averageTime * 1_000_000)) μs") + Log(.benchmark, "Fast .fastW: \(String(format: "%.2f", fastW.averageTime * 1_000_000)) μs") + Log(.benchmark, "Batch operation: \(String(format: "%.2f", batchOperation.averageTime * 1_000_000)) μs") let speedup = standardW.averageTime / fastW.averageTime - print("\n⚡ Fast path is \(String(format: "%.1f", speedup))x faster") + Log(.benchmark, "Fast path is \(String(format: "%.1f", speedup))x faster") } + #endif + /// Validates that all scaling operations produce positive results for a set of test inputs; returns `true` when all pass. public func validateScaling() -> Bool { let screenUtil = ScreenUtil.shared var allValid = true @@ -94,7 +95,7 @@ public struct ScreenUtilDebug: Sendable { (12, "Radius scaling") ] - print("\n✅ Validation Tests:") + Log(.debug, "Validation Tests:") for testCase in testCases { let widthResult = screenUtil.w(testCase.input) @@ -105,15 +106,16 @@ public struct ScreenUtilDebug: Sendable { let isValid = widthResult > 0 && heightResult > 0 && textResult > 0 && radiusResult > 0 if !isValid { allValid = false - print(" ❌ \(testCase.expected) failed for input \(testCase.input)") + Log(.debug, "\(testCase.expected) failed for input \(testCase.input)", level: .error) } else { - print(" ✅ \(testCase.expected) passed") + Log(.debug, "\(testCase.expected) passed") } } return allValid } + /// Generates a markdown-formatted test report containing device info, scale factors, and sample scaled values. public func generateTestReport() -> String { let screenUtil = ScreenUtil.shared let metrics = screenUtil.getScreenMetrics() @@ -157,6 +159,7 @@ public struct ScreenUtilDebug: Sendable { } #if canImport(UIKit) && DEBUG + /// Adds a semi-transparent overlay showing current screen metrics and scale factors to `view`. @MainActor public func showDebugOverlay(on view: UIView) { let overlayView = createDebugOverlay() view.addSubview(overlayView) @@ -213,16 +216,19 @@ public struct ScreenUtilDebug: Sendable { } public extension ScreenUtil { + /// Convenience accessor for `ScreenUtilDebug.shared`. var debug: ScreenUtilDebug { return ScreenUtilDebug.shared } } #if DEBUG +/// Prints the current ScreenUtil configuration to the log; available in DEBUG builds only. public func printScreenUtilInfo() { ScreenUtilDebug.shared.printCurrentConfiguration() } +/// Runs the ScreenUtil scaling benchmark and logs results; available in DEBUG builds only. public func benchmarkScreenUtil() { ScreenUtilDebug.shared.benchmarkScalingOperations() } diff --git a/Sources/ScreenUtil/Internal/Log.swift b/Sources/ScreenUtil/Internal/Log.swift new file mode 100644 index 0000000..b5950b3 --- /dev/null +++ b/Sources/ScreenUtil/Internal/Log.swift @@ -0,0 +1,44 @@ +// +// Log.swift +// ScreenUtil +// +// Unified-logging endpoints (os.Logger) for the package. +// Created by Dicky Darmawan on 09/06/26. +// + +import os + +/// Logging category — each maps to a dedicated `os.Logger` Console.app category. +enum LogCategory: String { + /// Library diagnostics (e.g. invalid input warnings). + case core + /// Developer console dumps from `ScreenUtilDebug`. + case debug + /// Benchmark output from `ScreenUtilDebug`. + case benchmark + + fileprivate var logger: Logger { + Logger(subsystem: "com.screenutil", category: rawValue) + } +} + +/// Severity of a `Log` call, mapped to the matching `os.Logger` level. +enum LogLevel { + case debug, info, notice, warning, error, fault +} + +/// Emit a message to the unified logging system (`os.Logger`). +/// +/// `os.Logger` is level-filtered by the system, so this is cheap in release +/// builds. All package log data is non-sensitive, so messages are `.public`. +func Log(_ category: LogCategory, _ message: String, level: LogLevel = .info) { + let logger = category.logger + switch level { + case .debug: logger.debug("\(message, privacy: .public)") + case .info: logger.info("\(message, privacy: .public)") + case .notice: logger.notice("\(message, privacy: .public)") + case .warning: logger.warning("\(message, privacy: .public)") + case .error: logger.error("\(message, privacy: .public)") + case .fault: logger.fault("\(message, privacy: .public)") + } +} diff --git a/Sources/ScreenUtil/Metrics/DeviceType.swift b/Sources/ScreenUtil/Metrics/DeviceType.swift index 7ef4cb5..76ff4d2 100644 --- a/Sources/ScreenUtil/Metrics/DeviceType.swift +++ b/Sources/ScreenUtil/Metrics/DeviceType.swift @@ -11,7 +11,7 @@ import Foundation import UIKit #endif -public enum DeviceType: Sendable { +public enum DeviceType: Sendable, Equatable, Hashable { case iPhone case iPad case mac diff --git a/Sources/ScreenUtil/Metrics/SafeAreaInsets.swift b/Sources/ScreenUtil/Metrics/SafeAreaInsets.swift index 416ead1..85be3ec 100644 --- a/Sources/ScreenUtil/Metrics/SafeAreaInsets.swift +++ b/Sources/ScreenUtil/Metrics/SafeAreaInsets.swift @@ -12,13 +12,20 @@ import CoreGraphics import UIKit #endif -public struct SafeAreaInsets: Sendable { +/// A value-type snapshot of the device safe-area insets and status bar height. +public struct SafeAreaInsets: Sendable, Equatable, Hashable { + /// Safe-area inset at the top of the screen in points. public let top: CGFloat + /// Safe-area inset at the bottom of the screen in points. public let bottom: CGFloat + /// Safe-area inset on the left side of the screen in points. public let left: CGFloat + /// Safe-area inset on the right side of the screen in points. public let right: CGFloat + /// Height of the status bar in points (0 when not applicable). public let statusBarHeight: CGFloat + /// Creates a `SafeAreaInsets` with explicit inset values and an optional status bar height. public init(top: CGFloat, bottom: CGFloat, left: CGFloat, right: CGFloat, statusBarHeight: CGFloat = 0) { self.top = top self.bottom = bottom @@ -27,6 +34,7 @@ public struct SafeAreaInsets: Sendable { self.statusBarHeight = statusBarHeight } + /// All-zero insets, useful as a safe default when no window is available. public static let zero = SafeAreaInsets(top: 0, bottom: 0, left: 0, right: 0) #if canImport(UIKit) diff --git a/Sources/ScreenUtil/Metrics/ScreenDimensions.swift b/Sources/ScreenUtil/Metrics/ScreenDimensions.swift index 074068a..8150a56 100644 --- a/Sources/ScreenUtil/Metrics/ScreenDimensions.swift +++ b/Sources/ScreenUtil/Metrics/ScreenDimensions.swift @@ -12,7 +12,7 @@ import CoreGraphics import UIKit #endif -public struct ScreenDimensions: Sendable { +public struct ScreenDimensions: Sendable, Equatable, Hashable { public let width: CGFloat public let height: CGFloat public let scale: CGFloat @@ -30,7 +30,11 @@ public struct ScreenDimensions: Sendable { #if canImport(UIKit) && !os(watchOS) @MainActor public static var current: ScreenDimensions { - let screen = UIScreen.main + guard let scene = UIApplication.shared.connectedScenes + .compactMap({ $0 as? UIWindowScene }).first else { + return platformDefault + } + let screen = scene.screen let bounds = screen.bounds return ScreenDimensions(width: bounds.width, height: bounds.height, scale: screen.scale) } diff --git a/Sources/ScreenUtil/Scaling/BatchScaling.swift b/Sources/ScreenUtil/Scaling/BatchScaling.swift index 3f1af79..b0b30a4 100644 --- a/Sources/ScreenUtil/Scaling/BatchScaling.swift +++ b/Sources/ScreenUtil/Scaling/BatchScaling.swift @@ -13,6 +13,7 @@ import UIKit #endif public extension ScreenUtil { + /// Scales an array of numeric values using the specified `scaleType`, returning scaled `CGFloat` values. @inline(__always) func batchScale(_ values: [T], scaleType: ScaleType) -> [CGFloat] { let cache = ScaleFactorCache(from: self) @@ -27,36 +28,42 @@ public extension ScreenUtil { } } + /// Scales an array of numeric values by the width factor, returning scaled `CGFloat` values. @inline(__always) func batchWidths(_ values: [T]) -> [CGFloat] { let cache = ScaleFactorCache(from: self) return values.map { cache.scaleWidth(cgFloatValue($0)) } } + /// Scales an array of numeric values by the height factor, returning scaled `CGFloat` values. @inline(__always) func batchHeights(_ values: [T]) -> [CGFloat] { let cache = ScaleFactorCache(from: self) return values.map { cache.scaleHeight(cgFloatValue($0)) } } + /// Scales an array of numeric values as font sizes using the text scale factor, returning scaled `CGFloat` values. @inline(__always) func batchFontSizes(_ values: [T]) -> [CGFloat] { let cache = ScaleFactorCache(from: self) return values.map { cache.scaleText(cgFloatValue($0)) } } + /// Scales an array of `CGSize` values, applying width and height factors independently. @inline(__always) func batchSizes(_ sizes: [CGSize]) -> [CGSize] { let cache = ScaleFactorCache(from: self) return sizes.map { CGSize(width: cache.scaleWidth($0.width), height: cache.scaleHeight($0.height)) } } + /// Scales an array of `CGPoint` values, applying width factor to x and height factor to y. @inline(__always) func batchPoints(_ points: [CGPoint]) -> [CGPoint] { let cache = ScaleFactorCache(from: self) return points.map { CGPoint(x: cache.scaleWidth($0.x), y: cache.scaleHeight($0.y)) } } + /// Scales an array of `CGRect` values, applying width and height factors to origin and size. @inline(__always) func batchRects(_ rects: [CGRect]) -> [CGRect] { let cache = ScaleFactorCache(from: self) @@ -71,6 +78,7 @@ public extension ScreenUtil { } #if canImport(UIKit) + /// Scales an array of `UIEdgeInsets` values, applying width factor to left/right and height factor to top/bottom. @inline(__always) func batchEdgeInsets(_ insets: [UIEdgeInsets]) -> [UIEdgeInsets] { let cache = ScaleFactorCache(from: self) @@ -86,6 +94,7 @@ public extension ScreenUtil { #endif } +/// Reusable batch-scaling context that captures scale factors once for efficient repeated scaling. public struct BatchScaler: Sendable { private let cache: ScaleFactorCache @@ -93,32 +102,38 @@ public struct BatchScaler: Sendable { self.cache = ScaleFactorCache(from: screenUtil) } + /// Scales an array of numeric values by the captured width factor. @inline(__always) public func widths(_ values: [T]) -> [CGFloat] { return values.map { cache.scaleWidth(cgFloatValue($0)) } } + /// Scales an array of numeric values by the captured height factor. @inline(__always) public func heights(_ values: [T]) -> [CGFloat] { return values.map { cache.scaleHeight(cgFloatValue($0)) } } + /// Scales an array of numeric values as font sizes using the captured text scale factor. @inline(__always) public func fontSizes(_ values: [T]) -> [CGFloat] { return values.map { cache.scaleText(cgFloatValue($0)) } } + /// Scales an array of numeric values using the smaller of the captured width/height factors. @inline(__always) public func radii(_ values: [T]) -> [CGFloat] { return values.map { cache.scaleRadius(cgFloatValue($0)) } } + /// Scales an array of `CGSize` values using the captured width and height factors. @inline(__always) public func sizes(_ sizes: [CGSize]) -> [CGSize] { return sizes.map { CGSize(width: cache.scaleWidth($0.width), height: cache.scaleHeight($0.height)) } } #if canImport(UIKit) + /// Scales an array of `UIEdgeInsets` values using the captured width and height factors. @inline(__always) public func edgeInsets(_ insets: [UIEdgeInsets]) -> [UIEdgeInsets] { return insets.map { inset in @@ -134,11 +149,13 @@ public struct BatchScaler: Sendable { } public extension ScreenUtil { + /// A `BatchScaler` pre-loaded with the current scale factors; reuse across multiple batch calls to amortise setup cost. var batchScaler: BatchScaler { return BatchScaler(from: self) } } +/// Captures the current scale factors once, passes a `BatchScaler` to `operation`, and returns its result. @inline(__always) public func withBatchScaler(_ operation: (BatchScaler) -> T) -> T { return operation(ScreenUtil.shared.batchScaler) diff --git a/Sources/ScreenUtil/Scaling/FastScale.swift b/Sources/ScreenUtil/Scaling/FastScale.swift index b9bf6d6..8eb1b0a 100644 --- a/Sources/ScreenUtil/Scaling/FastScale.swift +++ b/Sources/ScreenUtil/Scaling/FastScale.swift @@ -9,6 +9,7 @@ import Foundation import CoreGraphics +/// Capture-once scaling context that pre-stores scale factors for use in hot loops. public struct FastScale: Sendable { private let widthScale: CGFloat private let heightScale: CGFloat @@ -20,36 +21,43 @@ public struct FastScale: Sendable { self.textScale = textScale } + /// Returns `value` scaled by the captured width factor. @inline(__always) public func width(_ value: CGFloat) -> CGFloat { return value * widthScale } + /// Returns `value` scaled by the captured height factor. @inline(__always) public func height(_ value: CGFloat) -> CGFloat { return value * heightScale } + /// Returns `value` scaled by the captured text factor. @inline(__always) public func text(_ value: CGFloat) -> CGFloat { return value * textScale } + /// Returns `value` scaled by the smaller of the captured width and height factors. @inline(__always) public func radius(_ value: CGFloat) -> CGFloat { return value * min(widthScale, heightScale) } + /// Returns `value` with width and height each scaled by their respective captured factors. @inline(__always) public func size(_ value: CGSize) -> CGSize { return CGSize(width: value.width * widthScale, height: value.height * heightScale) } + /// Returns `value` with x scaled by the width factor and y by the height factor. @inline(__always) public func point(_ value: CGPoint) -> CGPoint { return CGPoint(x: value.x * widthScale, y: value.y * heightScale) } + /// Returns `value` with origin and size each scaled by their respective captured factors. @inline(__always) public func rect(_ value: CGRect) -> CGRect { return CGRect( @@ -62,6 +70,7 @@ public struct FastScale: Sendable { } public extension ScreenUtil { + /// A `FastScale` instance pre-loaded with the current scale factors; use this to avoid per-value singleton lookups. @inline(__always) var fastScale: FastScale { let s = _snapshot @@ -69,6 +78,7 @@ public extension ScreenUtil { } } +/// Captures the current scale factors once and passes a `FastScale` to `operation`, returning its result. @inline(__always) public func withFastScale(_ operation: (FastScale) -> T) -> T { return operation(ScreenUtil.shared.fastScale) diff --git a/Sources/ScreenUtil/Scaling/Numeric+Scaling.swift b/Sources/ScreenUtil/Scaling/Numeric+Scaling.swift index c3dcdb9..22e040c 100644 --- a/Sources/ScreenUtil/Scaling/Numeric+Scaling.swift +++ b/Sources/ScreenUtil/Scaling/Numeric+Scaling.swift @@ -10,46 +10,55 @@ import Foundation import CoreGraphics public extension Int { + /// Value scaled by the width factor (design width → device width). @inline(__always) var w: CGFloat { return ScreenUtil.shared.w(CGFloat(self)) } + /// Value scaled by the height factor (design height → device height). @inline(__always) var h: CGFloat { return ScreenUtil.shared.h(CGFloat(self)) } + /// Value scaled as a font size (uses min of width/height when `minTextAdapt` is enabled). @inline(__always) var sp: CGFloat { return ScreenUtil.shared.sp(CGFloat(self)) } + /// Value scaled by the smaller of width/height factors, suitable for corner radii. @inline(__always) var r: CGFloat { return ScreenUtil.shared.r(CGFloat(self)) } + /// Value expressed as a fraction of the screen width (0…1 → 0…screenWidth). @inline(__always) var sw: CGFloat { return ScreenUtil.shared.sw(CGFloat(self)) } + /// Value expressed as a fraction of the screen height (0…1 → 0…screenHeight). @inline(__always) var sh: CGFloat { return ScreenUtil.shared.sh(CGFloat(self)) } + /// Width-scaled value using the cached fast path (lower overhead than `.w` for tight loops). @inline(__always) var fastW: CGFloat { return ScreenUtil.shared.fastW(CGFloat(self)) } + /// Height-scaled value using the cached fast path (lower overhead than `.h` for tight loops). @inline(__always) var fastH: CGFloat { return ScreenUtil.shared.fastH(CGFloat(self)) } + /// Font-size-scaled value using the cached fast path (lower overhead than `.sp` for tight loops). @inline(__always) var fastSp: CGFloat { return ScreenUtil.shared.fastSp(CGFloat(self)) @@ -57,46 +66,55 @@ public extension Int { } public extension Float { + /// Value scaled by the width factor (design width → device width). @inline(__always) var w: CGFloat { return ScreenUtil.shared.w(CGFloat(self)) } + /// Value scaled by the height factor (design height → device height). @inline(__always) var h: CGFloat { return ScreenUtil.shared.h(CGFloat(self)) } + /// Value scaled as a font size (uses min of width/height when `minTextAdapt` is enabled). @inline(__always) var sp: CGFloat { return ScreenUtil.shared.sp(CGFloat(self)) } + /// Value scaled by the smaller of width/height factors, suitable for corner radii. @inline(__always) var r: CGFloat { return ScreenUtil.shared.r(CGFloat(self)) } + /// Value expressed as a fraction of the screen width (0…1 → 0…screenWidth). @inline(__always) var sw: CGFloat { return ScreenUtil.shared.sw(CGFloat(self)) } + /// Value expressed as a fraction of the screen height (0…1 → 0…screenHeight). @inline(__always) var sh: CGFloat { return ScreenUtil.shared.sh(CGFloat(self)) } + /// Width-scaled value using the cached fast path (lower overhead than `.w` for tight loops). @inline(__always) var fastW: CGFloat { return ScreenUtil.shared.fastW(CGFloat(self)) } + /// Height-scaled value using the cached fast path (lower overhead than `.h` for tight loops). @inline(__always) var fastH: CGFloat { return ScreenUtil.shared.fastH(CGFloat(self)) } + /// Font-size-scaled value using the cached fast path (lower overhead than `.sp` for tight loops). @inline(__always) var fastSp: CGFloat { return ScreenUtil.shared.fastSp(CGFloat(self)) @@ -104,46 +122,55 @@ public extension Float { } public extension Double { + /// Value scaled by the width factor (design width → device width). @inline(__always) var w: CGFloat { return ScreenUtil.shared.w(CGFloat(self)) } + /// Value scaled by the height factor (design height → device height). @inline(__always) var h: CGFloat { return ScreenUtil.shared.h(CGFloat(self)) } + /// Value scaled as a font size (uses min of width/height when `minTextAdapt` is enabled). @inline(__always) var sp: CGFloat { return ScreenUtil.shared.sp(CGFloat(self)) } + /// Value scaled by the smaller of width/height factors, suitable for corner radii. @inline(__always) var r: CGFloat { return ScreenUtil.shared.r(CGFloat(self)) } + /// Value expressed as a fraction of the screen width (0…1 → 0…screenWidth). @inline(__always) var sw: CGFloat { return ScreenUtil.shared.sw(CGFloat(self)) } + /// Value expressed as a fraction of the screen height (0…1 → 0…screenHeight). @inline(__always) var sh: CGFloat { return ScreenUtil.shared.sh(CGFloat(self)) } + /// Width-scaled value using the cached fast path (lower overhead than `.w` for tight loops). @inline(__always) var fastW: CGFloat { return ScreenUtil.shared.fastW(CGFloat(self)) } + /// Height-scaled value using the cached fast path (lower overhead than `.h` for tight loops). @inline(__always) var fastH: CGFloat { return ScreenUtil.shared.fastH(CGFloat(self)) } + /// Font-size-scaled value using the cached fast path (lower overhead than `.sp` for tight loops). @inline(__always) var fastSp: CGFloat { return ScreenUtil.shared.fastSp(CGFloat(self)) @@ -151,46 +178,55 @@ public extension Double { } public extension CGFloat { + /// Value scaled by the width factor (design width → device width). @inline(__always) var w: CGFloat { return ScreenUtil.shared.w(self) } + /// Value scaled by the height factor (design height → device height). @inline(__always) var h: CGFloat { return ScreenUtil.shared.h(self) } + /// Value scaled as a font size (uses min of width/height when `minTextAdapt` is enabled). @inline(__always) var sp: CGFloat { return ScreenUtil.shared.sp(self) } + /// Value scaled by the smaller of width/height factors, suitable for corner radii. @inline(__always) var r: CGFloat { return ScreenUtil.shared.r(self) } + /// Value expressed as a fraction of the screen width (0…1 → 0…screenWidth). @inline(__always) var sw: CGFloat { return ScreenUtil.shared.sw(self) } + /// Value expressed as a fraction of the screen height (0…1 → 0…screenHeight). @inline(__always) var sh: CGFloat { return ScreenUtil.shared.sh(self) } + /// Width-scaled value using the cached fast path (lower overhead than `.w` for tight loops). @inline(__always) var fastW: CGFloat { return ScreenUtil.shared.fastW(self) } + /// Height-scaled value using the cached fast path (lower overhead than `.h` for tight loops). @inline(__always) var fastH: CGFloat { return ScreenUtil.shared.fastH(self) } + /// Font-size-scaled value using the cached fast path (lower overhead than `.sp` for tight loops). @inline(__always) var fastSp: CGFloat { return ScreenUtil.shared.fastSp(self) diff --git a/Sources/ScreenUtil/SwiftUI/ScaledValue.swift b/Sources/ScreenUtil/SwiftUI/ScaledValue.swift index ae5ae77..7b26f49 100644 --- a/Sources/ScreenUtil/SwiftUI/ScaledValue.swift +++ b/Sources/ScreenUtil/SwiftUI/ScaledValue.swift @@ -10,43 +10,27 @@ import SwiftUI /// Property wrapper for a design value scaled to the device. -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @propertyWrapper public struct ScaledValue { private let value: CGFloat private let type: ScaleType - public enum ScaleType { - case width - case height - case font - case radius - case auto - } - - public init(wrappedValue: CGFloat, _ type: ScaleType = .auto) { + public init(wrappedValue: CGFloat, _ type: ScaleType = .width) { self.value = wrappedValue self.type = type } public var wrappedValue: CGFloat { switch type { - case .width: - return value.w - case .height: - return value.h - case .font: - return value.sp - case .radius: - return value.r - case .auto: - return value.w + case .width: return value.w + case .height: return value.h + case .text: return value.sp + case .radius: return value.r } } } /// Property wrapper for a percentage of a screen dimension. -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @propertyWrapper public struct ScreenPercentage { private let percentage: CGFloat diff --git a/Sources/ScreenUtil/SwiftUI/View+Responsive.swift b/Sources/ScreenUtil/SwiftUI/View+Responsive.swift index 5ef8add..5d9dc3f 100644 --- a/Sources/ScreenUtil/SwiftUI/View+Responsive.swift +++ b/Sources/ScreenUtil/SwiftUI/View+Responsive.swift @@ -11,7 +11,6 @@ import SwiftUI // MARK: - View Modifiers -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension View { /// Apply a responsive frame (design values scaled to the device). func responsiveFrame( @@ -45,7 +44,6 @@ public extension View { // MARK: - Font -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension Font { /// Create a system font with scaled size. static func scaledSystem( @@ -64,12 +62,10 @@ public extension Font { // MARK: - Environment -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private struct ScreenUtilKey: EnvironmentKey { static let defaultValue: ScreenUtil = .shared } -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public extension EnvironmentValues { /// Access ScreenUtil instance from environment. var screenUtil: ScreenUtil { @@ -80,7 +76,6 @@ public extension EnvironmentValues { // MARK: - Helpers -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) private extension Edge.Set { var isHorizontal: Bool { return self == .horizontal || self == .leading || self == .trailing diff --git a/Tests/ScreenUtilTests/Core/EquatableConformanceTests.swift b/Tests/ScreenUtilTests/Core/EquatableConformanceTests.swift new file mode 100644 index 0000000..41cea00 --- /dev/null +++ b/Tests/ScreenUtilTests/Core/EquatableConformanceTests.swift @@ -0,0 +1,51 @@ +// +// EquatableConformanceTests.swift +// ScreenUtilTests +// +// Equatable/Hashable conformance for public value types +// Created by Dicky Darmawan on 09/06/26. +// + +import XCTest +@testable import ScreenUtil + +final class EquatableConformanceTests: XCTestCase { + + func testScalingLimitsEquatable() { + XCTAssertEqual(ScalingLimits(minScale: 0.5, maxScale: 2.0), + ScalingLimits(minScale: 0.5, maxScale: 2.0)) + XCTAssertNotEqual(ScalingLimits.strict, ScalingLimits.relaxed) + } + + func testConfigurationEquatable() { + XCTAssertEqual(ScreenUtilConfiguration.iPhoneX, ScreenUtilConfiguration.iPhoneX) + XCTAssertNotEqual(ScreenUtilConfiguration.iPhoneX, ScreenUtilConfiguration.iPhone8) + } + + func testScaleTypeHashable() { + let set: Set = [.width, .height, .text, .radius, .width] + XCTAssertEqual(set.count, 4) + } + + func testDeviceTypeEquatable() { + XCTAssertEqual(DeviceType.iPhone, DeviceType.iPhone) + XCTAssertNotEqual(DeviceType.iPhone, DeviceType.iPad) + } + + func testScreenMetricsEquatable() { + let a = ScreenMetrics(width: 100, height: 200, scale: 2, + safeAreaInsets: (1, 2, 3, 4), statusBarHeight: 5) + let b = ScreenMetrics(width: 100, height: 200, scale: 2, + safeAreaInsets: (1, 2, 3, 4), statusBarHeight: 5) + let c = ScreenMetrics(width: 100, height: 200, scale: 2, + safeAreaInsets: (9, 2, 3, 4), statusBarHeight: 5) + XCTAssertEqual(a, b) + XCTAssertNotEqual(a, c) + } + + func testSafeAreaInsetsAndDimensionsEquatable() { + XCTAssertEqual(SafeAreaInsets.zero, SafeAreaInsets(top: 0, bottom: 0, left: 0, right: 0)) + XCTAssertEqual(ScreenDimensions(width: 10, height: 20, scale: 2), + ScreenDimensions(width: 10, height: 20, scale: 2)) + } +} diff --git a/Tests/ScreenUtilTests/Core/ScreenUtilTests.swift b/Tests/ScreenUtilTests/Core/ScreenUtilTests.swift index b9f3489..5390bde 100644 --- a/Tests/ScreenUtilTests/Core/ScreenUtilTests.swift +++ b/Tests/ScreenUtilTests/Core/ScreenUtilTests.swift @@ -18,7 +18,6 @@ final class ScreenUtilTests: XCTestCase { let testConfig = ScreenUtilConfiguration( designSize: CGSize(width: 375, height: 812), minTextAdapt: true, - splitScreenMode: true, scalingLimits: ScalingLimits() ) let su = screenUtil! @@ -42,7 +41,6 @@ final class ScreenUtilTests: XCTestCase { let config = ScreenUtilConfiguration( designSize: CGSize(width: 390, height: 844), minTextAdapt: false, - splitScreenMode: false, scalingLimits: ScalingLimits(minScale: 0.8, maxScale: 1.5) ) diff --git a/Tests/ScreenUtilTests/SwiftUI/PropertyWrapperTests.swift b/Tests/ScreenUtilTests/SwiftUI/PropertyWrapperTests.swift index 5bdde54..d0c0569 100644 --- a/Tests/ScreenUtilTests/SwiftUI/PropertyWrapperTests.swift +++ b/Tests/ScreenUtilTests/SwiftUI/PropertyWrapperTests.swift @@ -18,7 +18,7 @@ final class PropertyWrapperTests: XCTestCase { scalingLimits: ScalingLimits(minScale: 0.1, maxScale: 10))) let w = ScaledValue(wrappedValue: 10, .width) XCTAssertEqual(w.wrappedValue, 10 * su.scaleWidth, accuracy: 0.001) - let f = ScaledValue(wrappedValue: 16, .font) + let f = ScaledValue(wrappedValue: 16, .text) XCTAssertEqual(f.wrappedValue, 16 * su.scaleText, accuracy: 0.001) } @@ -31,7 +31,7 @@ final class PropertyWrapperTests: XCTestCase { } @MainActor - func testScaledValueHeightRadiusAuto() { + func testScaledValueHeightRadius() { let su = ScreenUtil.shared su.configure(with: ScreenUtilConfiguration(designSize: CGSize(width: 100, height: 100), scalingLimits: ScalingLimits(minScale: 0.1, maxScale: 10))) @@ -40,9 +40,6 @@ final class PropertyWrapperTests: XCTestCase { let r = ScaledValue(wrappedValue: 8, .radius) XCTAssertEqual(r.wrappedValue, su.r(8), accuracy: 0.001) - - let a = ScaledValue(wrappedValue: 12, .auto) - XCTAssertEqual(a.wrappedValue, 12 * su.scaleWidth, accuracy: 0.001) } @MainActor