You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何根据iOS版本声明不同的ViewModel存储属性?

兼容iOS 16及以下版本的SwiftUI图片选择ViewModel实现

要在同一个ViewModel中兼容iOS 16+的PhotosPickerItem和旧版本的UIImage,可以通过条件编译结合通用逻辑抽离的方式实现,无需拆分ViewModel。

核心实现思路

  1. 用条件编译指令分别声明不同iOS版本的@Published属性,确保对应版本的代码仅在目标系统编译
  2. 将属性变化后的处理逻辑抽进通用方法,避免重复代码
  3. 对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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 01:59:55