80 lines
3.0 KiB
Swift
80 lines
3.0 KiB
Swift
//
|
|
// 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<CGFloat> = 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
|
|
}
|
|
}
|