Files
suixinkan_uikit/suixinkan/Features/TravelAlbum/ViewModels/BeforeAfterComparisonViewModel.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
}
}