// // BeforeAfterComparisonViewModel.swift // suixinkan // import Foundation /// 前后对比分隔线的共享运动参数与计算规则。 enum BeforeAfterComparisonMotion { static let automaticCenter: CGFloat = 0.5 static let automaticAmplitude: CGFloat = 0.3 static let automaticSpeed: Double = 0.8 static let manualRange: ClosedRange = 0.05 ... 0.95 /// 返回指定时间点的自动往返位置,运动范围固定为 20% 至 80%。 static func automaticFraction(elapsed: TimeInterval) -> CGFloat { automaticCenter + CGFloat(sin(elapsed * automaticSpeed)) * automaticAmplitude } /// 将手动拖动位置限制在两侧 5% 的安全边界内。 static func clampedManualFraction(_ fraction: CGFloat) -> CGFloat { min(manualRange.upperBound, max(manualRange.lowerBound, fraction)) } /// 计算从当前位置恢复正向自动运动所需的正弦相位时间。 static func phaseTime(for fraction: CGFloat) -> TimeInterval { let normalized = Double( min(1, max(-1, (fraction - automaticCenter) / automaticAmplitude)) ) return asin(normalized) / automaticSpeed } } /// 前后对比图片在可用区域内完整展示时的等比布局计算。 enum BeforeAfterComparisonLayout { static let fallbackAspectRatio: CGFloat = 3.0 / 4.0 /// 从图片尺寸提取有效宽高比,异常尺寸回退为 3:4。 static func aspectRatio(for imageSize: CGSize) -> CGFloat { guard imageSize.width > 0, imageSize.height > 0 else { return fallbackAspectRatio } let ratio = imageSize.width / imageSize.height return ratio.isFinite && ratio > 0 ? ratio : fallbackAspectRatio } /// 在最大宽高内返回不裁剪、不拉伸的最大显示尺寸。 static func fittedSize(aspectRatio: CGFloat, maximumSize: CGSize) -> CGSize { guard maximumSize.width > 0, maximumSize.height > 0 else { return .zero } let ratio = aspectRatio.isFinite && aspectRatio > 0 ? aspectRatio : fallbackAspectRatio if maximumSize.width / maximumSize.height > ratio { return CGSize(width: maximumSize.height * ratio, height: maximumSize.height) } return CGSize(width: maximumSize.width, height: maximumSize.width / ratio) } } /// 前后图片对比页的只读状态,负责校验并解析两张远程图片地址。 final class BeforeAfterComparisonViewModel { let content: BeforeAfterComparisonContent /// 创建指定内容的前后对比页状态。 init(content: BeforeAfterComparisonContent) { self.content = content } /// 原图远程地址。 var beforeImageURL: URL? { URL(string: content.beforeURL) } /// 效果图远程地址。 var afterImageURL: URL? { URL(string: content.afterURL) } /// 两张图片地址都可供页面加载时返回 true。 var isValid: Bool { content.isValid && beforeImageURL != nil && afterImageURL != nil } }