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

SwiftUI中为UIImage实现.draggable与.dropDestination拖拽功能

实现SwiftUI照片拼贴的拖拽交换功能

针对你的需求,我们可以通过自定义Transferable数据类型结合.draggable和.dropDestination来实现照片插槽间的拖拽交换,比直接传递UIImage更高效且符合MVVM架构。以下是分步实现方案:

1. 定义拖拽数据类型

创建一个自定义结构体,用来传递拖拽的源位置信息(页面ID和图片索引),避免直接传递大体积的UIImage:

import SwiftUI
import UniformTypeIdentifiers

struct DraggablePhotoInfo: Transferable {
    let pageID: Int
    let photoIndex: Int
    
    static var transferRepresentation: some TransferRepresentation {
        // 使用自定义UTI标识数据类型,避免与系统类型冲突
        DataRepresentation(exportedContentType: .init("com.yourapp.draggable-photo-info")) { info in
            try JSONEncoder().encode(info)
        } importing: { data in
            try JSONDecoder().decode(Self.self, from: data)
        }
    }
}

2. 修改PhotoPlaceholderView添加拖拽交互

更新占位视图,添加.draggable(仅当有图片时启用)和.dropDestination修饰符:

struct PhotoPlaceholderView: View {
    @ObservedObject var vm: EditorViewModel
    var pageID: Int
    var photoIndex: Int
    let width: CGFloat
    let height: CGFloat
    
    private var hasImage: Bool {
        vm.pages[pageID]?.images[photoIndex] != nil
    }
    
    var body: some View {
        Rectangle()
            .fill(hasImage ? Color.clear : Color.gray)
            .frame(width: width, height: height)
            .overlay(
                Group {
                    if hasImage {
                        Image(uiImage: vm.pages[pageID]!.images[photoIndex]!)
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                    } else {
                        Image(systemName: "photo")
                            .foregroundColor(.white)
                    }
                }
            )
            .clipped()
            .onTapGesture {
                vm.activeImageIndex = photoIndex
                vm.isMultipleSelection = false
                vm.isImagePickerPresented = true
            }
            // 仅当有图片时允许拖拽
            .draggable(hasImage ? DraggablePhotoInfo(pageID: pageID, photoIndex: photoIndex) : nil)
            // 接收拖拽的目标处理
            .dropDestination(for: DraggablePhotoInfo.self) { items, location in
                guard let sourceInfo = items.first else { return false }
                // 避免拖拽到自身位置
                guard sourceInfo.pageID != pageID || sourceInfo.photoIndex != photoIndex else { return false }
                
                // 调用ViewModel的交换逻辑
                vm.swapImages(fromPage: sourceInfo.pageID, fromIndex: sourceInfo.photoIndex, toPage: pageID, toIndex: photoIndex)
                return true
            }
    }
}

3. 在ViewModel中实现图片交换逻辑

给EditorViewModel添加交换图片的方法,负责处理数据层的更新:

class EditorViewModel: ObservableObject {
    @Published var pages: [Int: CollagePage] = [:]
    @Published var activeImageIndex: Int = 0
    @Published var isMultipleSelection: Bool = false
    @Published var isImagePickerPresented: Bool = false
    
    func addPage() {
        let newID = (pages.keys.max() ?? 0) + 1
        pages[newID] = CollagePage(id: newID)
    }
    
    // 新增图片交换方法
    func swapImages(fromPage sourcePageID: Int, fromIndex sourceIndex: Int, toPage targetPageID: Int, toIndex targetIndex: Int) {
        guard var sourcePage = pages[sourcePageID],
              var targetPage = pages[targetPageID],
              let sourceImage = sourcePage.images[sourceIndex] else { return }
        
        // 交换源和目标位置的图片(支持目标位置为空的情况)
        let targetImage = targetPage.images[targetIndex]
        sourcePage.images[sourceIndex] = targetImage
        targetPage.images[targetIndex] = sourceImage
        
        // 更新ViewModel中的页面数据
        pages[sourcePageID] = sourcePage
        pages[targetPageID] = targetPage
    }
}

可选:让UIImage支持Transferable(用于外部图片拖拽)

如果需要支持从系统相册或其他App拖拽图片到你的拼贴插槽,可以扩展UIImage遵循Transferable:

extension UIImage: Transferable {
    public static var transferRepresentation: some TransferRepresentation {
        DataRepresentation(exportedContentType: .image) { image in
            guard let data = image.pngData() else { throw TransferError.exportFailed }
            return data
        } importing: { data in
            guard let image = UIImage(data: data) else { throw TransferError.importFailed }
            return image
        }
    }
    
    enum TransferError: Error {
        case exportFailed
        case importFailed
    }
}

此时你可以在PhotoPlaceholderView的.dropDestination中添加对UIImage.self的支持,实现外部图片导入功能。

内容的提问来源于stack exchange,提问作者user1207524

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 12:36:00