110 lines
3.3 KiB
Swift
110 lines
3.3 KiB
Swift
//
|
||
// MediaPreviewItem.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import UIKit
|
||
|
||
/// 媒体预览资源项,描述可全屏预览的图片或视频。
|
||
public struct MediaPreviewItem: Hashable {
|
||
|
||
/// 媒体类型。
|
||
public enum MediaType: Equatable {
|
||
case image
|
||
case video
|
||
}
|
||
|
||
/// 预览资源来源。
|
||
public enum Source {
|
||
case remoteImage(URL)
|
||
case localImageFile(URL)
|
||
case image(UIImage)
|
||
case remoteVideo(URL)
|
||
case localVideoFile(URL)
|
||
}
|
||
|
||
/// 资源唯一标识,用于 Diffable Data Source。
|
||
public let id: UUID
|
||
|
||
/// 预览资源来源。
|
||
public let source: Source
|
||
|
||
/// 创建媒体预览资源项。
|
||
public init(id: UUID = UUID(), source: Source) {
|
||
self.id = id
|
||
self.source = source
|
||
}
|
||
|
||
/// 媒体类型。
|
||
public var mediaType: MediaType {
|
||
switch source {
|
||
case .remoteImage, .localImageFile, .image:
|
||
return .image
|
||
case .remoteVideo, .localVideoFile:
|
||
return .video
|
||
}
|
||
}
|
||
|
||
/// 是否为图片资源。
|
||
public var isImage: Bool {
|
||
mediaType == .image
|
||
}
|
||
|
||
/// 是否为视频资源。
|
||
public var isVideo: Bool {
|
||
mediaType == .video
|
||
}
|
||
|
||
/// 通过远程图片地址创建资源项,空字符串或非法地址返回 nil。
|
||
public static func remoteImage(_ urlString: String) -> MediaPreviewItem? {
|
||
makeRemoteURL(from: urlString).map { MediaPreviewItem(source: .remoteImage($0)) }
|
||
}
|
||
|
||
/// 通过远程视频地址创建资源项,空字符串或非法地址返回 nil。
|
||
public static func remoteVideo(_ urlString: String) -> MediaPreviewItem? {
|
||
makeRemoteURL(from: urlString).map { MediaPreviewItem(source: .remoteVideo($0)) }
|
||
}
|
||
|
||
/// 通过本地图片文件地址创建资源项。
|
||
public static func localImage(url: URL) -> MediaPreviewItem {
|
||
MediaPreviewItem(source: .localImageFile(url))
|
||
}
|
||
|
||
/// 通过本地图片对象创建资源项。
|
||
public static func image(_ image: UIImage) -> MediaPreviewItem {
|
||
MediaPreviewItem(source: .image(image))
|
||
}
|
||
|
||
/// 通过本地视频文件地址创建资源项。
|
||
public static func localVideo(url: URL) -> MediaPreviewItem {
|
||
MediaPreviewItem(source: .localVideoFile(url))
|
||
}
|
||
|
||
/// 将起始索引限制在可用范围内。
|
||
public static func clampedStartIndex(_ startIndex: Int, itemCount: Int) -> Int {
|
||
guard itemCount > 0 else { return 0 }
|
||
return min(max(0, startIndex), itemCount - 1)
|
||
}
|
||
|
||
/// 从媒体数组中读取安全的起始资源项。
|
||
public static func currentItem(in items: [MediaPreviewItem], startIndex: Int) -> MediaPreviewItem? {
|
||
guard !items.isEmpty else { return nil }
|
||
return items[clampedStartIndex(startIndex, itemCount: items.count)]
|
||
}
|
||
|
||
public static func == (lhs: MediaPreviewItem, rhs: MediaPreviewItem) -> Bool {
|
||
lhs.id == rhs.id
|
||
}
|
||
|
||
public func hash(into hasher: inout Hasher) {
|
||
hasher.combine(id)
|
||
}
|
||
|
||
private static func makeRemoteURL(from text: String) -> URL? {
|
||
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
guard !trimmed.isEmpty, let url = URL(string: trimmed) else { return nil }
|
||
guard let scheme = url.scheme?.lowercased(), ["http", "https"].contains(scheme) else { return nil }
|
||
return url
|
||
}
|
||
}
|