SwiftUI中如何根据iOS版本声明不同的ViewModel存储属性?
兼容iOS 16及以下版本的SwiftUI图片选择ViewModel实现
要在同一个ViewModel中兼容iOS 16+的PhotosPickerItem和旧版本的UIImage,可以通过条件编译结合通用逻辑抽离的方式实现,无需拆分ViewModel。
核心实现思路
- 用条件编译指令分别声明不同iOS版本的
@Published属性,确保对应版本的代码仅在目标系统编译 - 将属性变化后的处理逻辑抽进通用方法,避免重复代码
- 对iOS 16+专属的方法(如
loadTransferable)也用条件编译包裹,防止旧版本编译报错
ViewModel代码示例
import SwiftUI #if compiler(>=5.7) && os(iOS) import PhotosUI #endif class NoteViewModel: ObservableObject { @Published var noteTitle: String = "" @Published var noteText: String = "" // 图片加载状态枚举,根据你的业务需求定义 @Published var imageAttachmentState: ImageAttachmentState = .empty // MARK: - iOS 16+ 专属图片选择属性 #if compiler(>=5.7) && os(iOS) && iOS_VERSION_GREATER_THAN_OR_EQUAL_TO(160000) @Published var imageSelection: PhotosPickerItem? = nil { didSet { handleImageSelectionUpdate(for: imageSelection) } } #endif // MARK: - iOS <16 图片选择属性 #if os(iOS) && !iOS_VERSION_GREATER_THAN_OR_EQUAL_TO(160000) @Published var imageSelection: UIImage? = nil { didSet { handleImageSelectionUpdate(for: imageSelection) } } #endif // MARK: - 通用图片选择处理逻辑 private func handleImageSelectionUpdate<T>(for selection: T?) { guard let selection = selection else { imageAttachmentState = .empty return } // 分类型处理不同版本的选择结果 if let pickerItem = selection as? PhotosPickerItem { let progress = loadTransferable(from: pickerItem) imageAttachmentState = .loading(progress) } else if let image = selection as? UIImage { imageAttachmentState = .loaded(image) // 这里添加旧版本图片选择后的业务逻辑 } } // MARK: - iOS 16+ 专属图片加载方法 #if compiler(>=5.7) && os(iOS) && iOS_VERSION_GREATER_THAN_OR_EQUAL_TO(160000) private func loadTransferable(from item: PhotosPickerItem) -> Progress { let progress = Progress() Task { do { if let image = try await item.loadTransferable(type: UIImage.self) { DispatchQueue.main.async { self.imageAttachmentState = .loaded(image) } } } catch { DispatchQueue.main.async { self.imageAttachmentState = .error(error) } } } return progress } #endif // MARK: - 图片状态枚举 enum ImageAttachmentState { case empty case loading(Progress) case loaded(UIImage) case error(Error) } }
关键配置说明
- 编译宏设置: 如果项目中未定义
iOS_VERSION_GREATER_THAN_OR_EQUAL_TO,需要在Build Settings->Preprocessor Macros中添加:iOS_VERSION_GREATER_THAN_OR_EQUAL_TO(160000)=$(greaterThanOrEqualTo(ios16_0)) - View层适配: 在View中同样用条件编译分别渲染
PhotosPicker(iOS16+)和UIImagePickerController(旧版本),确保与ViewModel属性对应:struct NoteView: View { @StateObject var viewModel = NoteViewModel() var body: some View { VStack { // 其他UI元素... #if compiler(>=5.7) && os(iOS) && iOS_VERSION_GREATER_THAN_OR_EQUAL_TO(160000) PhotosPicker(selection: $viewModel.imageSelection, matching: .images) { Text("选择图片") } #else Button("选择图片") { let picker = UIImagePickerController() picker.delegate = self // 获取顶层ViewController并弹出选择器 UIApplication.shared.topViewController?.present(picker, animated: true) } #endif // 根据状态渲染图片/加载提示 switch viewModel.imageAttachmentState { case .empty: Text("未选择图片") case .loading(let progress): ProgressView(progress) case .loaded(let image): Image(uiImage: image).resizable().scaledToFit() case .error(let error): Text("加载失败: \(error.localizedDescription)") } } } } // 旧版本图片选择器代理实现 extension NoteView: UIImagePickerControllerDelegate, UINavigationControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { picker.dismiss(animated: true) if let image = info[.originalImage] as? UIImage { viewModel.imageSelection = image } } } // 辅助扩展:获取顶层ViewController extension UIApplication { var topViewController: UIViewController? { connectedScenes .filter { $0.activationState == .foregroundActive } .compactMap { $0 as? UIWindowScene } .first? .windows .first(where: \.isKeyWindow)? .rootViewController? .topMost() } } extension UIViewController { func topMost() -> UIViewController { presentedViewController?.topMost() ?? self } }
方案优势
- 无需拆分ViewModel,所有业务逻辑集中维护
- 条件编译确保代码仅在对应系统版本生效,避免运行时兼容性错误
- 通用方法抽离重复逻辑,减少代码冗余
内容的提问来源于stack exchange,提问作者Giumex
相关产品推荐
相关产品推荐

