美文网首页
swiftui 九宫格拖动排序

swiftui 九宫格拖动排序

作者: 光彩影 | 来源:发表于2024-05-15 10:59 被阅读0次

这适合没拖动到底部删除

import SwiftUI
import UniformTypeIdentifiers

struct ContentView: View {
    
    @StateObject var vm = VM()
    
    var body: some View {
        NavigationView{
            VStack{
                imageContents
                if(vm.draggingItem != nil){
                    bottomView
                }
                
            }
            .frame(maxWidth: .infinity,maxHeight: .infinity)
            .navigationBarTitle("可移动的九宫格", displayMode: .inline)
        }
        
    }
    
    var imageContents:some View{
        ScrollView {
            GeometryReader { geometry in
                // 创建LazyVGrid
                LazyVGrid(columns: vm.gridLayout, spacing: 12) {
                    ForEach(Array(vm.images.enumerated()), id: \.element) { index, item in
                        if(item.hasAdd){
                            itemView(item: item,width: (geometry.size.width - 24 - 32) / 3)
                                .onTapGesture {
                                    vm.imagePickerPresented()
                                }
                        }else{
                            itemView(item: item,width: (geometry.size.width - 24 - 32) / 3)
                                .onTapGesture {
                                    vm.imageBrowserPresented(index: index)
                                }
                                .onDrag {
                                    vm.draggingItem = item
                                    let provider = NSDelItemProvider(contentsOf: URL(string: "\(item.id)"))!
                                        provider.didEnd = {
                                            DispatchQueue.main.async {
                                                print("didEnd")
                                                vm.draggingItem = nil      // << here !!
                                            }
                                        }
                                    return provider
                                }
                                .onDrop(of: [.item], delegate: ImageMoveItemDropDelegate(item: item, vm: vm))
                        }
                    }
                }
                .padding(.horizontal, 16) // 增加水平方向的内边距
            }
        }
    }
    
    func itemView(item:ImageMoveItem,width:CGFloat) -> some View{
        ZStack{
            Image(uiImage:item.uiImage)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: item.hasAdd ? 32 : width,height: item.hasAdd ? 32 : width)
                .cornerRadius(8)
                .allowsHitTesting(false)
        }
        .frame(width:width,height: width)
        .cornerRadius(8)
        .overlay {
            if(item.hasAdd){
                RoundedRectangle(cornerRadius: 8, style: .continuous)
                    .stroke(.gray, lineWidth: 1)
            }
        }
        .contentShape(Rectangle())
    }
    
    var bottomView:some View{
        ZStack(alignment: .top, content: {
            Rectangle()
                .foregroundColor(.white)
                .frame(height: 36 + 53)
                .onDrop(of: [.item], delegate: ImageDelItemDropDelegate(vm: vm))
            Rectangle()
                .frame(height: 36)
                .foregroundColor(vm.isDraggingInDelView ? Color.red : Color.red.opacity(0.8))
                .padding(.top,53)
            Text("拖动到此处删除")
                .font(.system(size: 16).bold())
                .foregroundStyle(.white)
                .padding(10)
                .padding(.top,53)
        })
        .background(vm.isDraggingInDelView ? Color.red : Color.red.opacity(0.8))
        .background(ignoresSafeAreaEdges: .bottom)
    }
}

#Preview {
    ContentView()
}

//
//  VM.swift
//  testSign
//
//  Created by 1 on 2024/5/14.
//

import Foundation
import UIKit
import SwiftUI
import JFHeroBrowser

let keyWindow = UIApplication.shared.connectedScenes
                        .map({ $0 as? UIWindowScene })
                        .compactMap({ $0 })
                        .first?.windows.first

let myAppRootVC : UIViewController? = keyWindow?.rootViewController

let addImageMoveItem:ImageMoveItem = ImageMoveItem(uiImage: UIImage(named: "ic_imgadd") ?? UIImage(systemName: "plus")!,hasAdd: true)
let testImageMoveItem:ImageMoveItem = ImageMoveItem(uiImage: UIImage(systemName: "move.3d")!)

class VM: ObservableObject{
//    @Published var isImagePickerPresented = false
    
    // 定义网格布局
    let gridLayout: [GridItem] = Array(repeating: .init(.flexible()), count: 3)
    
    var photoPicker = QSPhotoPicker()
    @Published var images:[ImageMoveItem] = [
        testImageMoveItem,addImageMoveItem,
    ]
    var maxImages:Int = 9
    var getTrueImageNum:Int{
        get{
            var getTrueImageNum = 0
            for image in images {
                if(!image.hasAdd){
                    getTrueImageNum = getTrueImageNum+1
                }
            }
            return getTrueImageNum
        }
    }
    
    //拖拽
    @Published var draggingItem: ImageMoveItem?
    @Published var isDragging = false
    // 用于标识是否有项目被拖动到删除区域
    @Published var isDraggingInDelView: Bool = false

    init() {

    }

    func imageBrowserPresented(index:Int){
        var imageBrowser = QSImageBrowser()
        var list: [UIImage] = []
        for image in images {
            if(!image.hasAdd){
                list.append(image.uiImage)
            }
        }
        imageBrowser.uiimages = list
        imageBrowser.imageBrowserPresented(index: index)
    }
    
    func imagePickerPresented(){
        photoPicker.maxSelectCount = self.maxImages - self.getTrueImageNum
        photoPicker.selectImageBlock = { results in
            var tempsImages:[ImageMoveItem] = []
            for item in results{
                tempsImages.append(ImageMoveItem(uiImage: item))
            }
            let allImages = self.getTrueImageNum + tempsImages.count
            if(allImages == self.maxImages){//去除+
                self.images.removeLast()
                self.images.append(contentsOf: tempsImages)
            }else{
                self.images.insert(contentsOf: tempsImages, at: self.images.count-1)
            }
        }
        photoPicker.imagePickerPresented()
    }
}

//MARK: Drop Delegate
struct ImageMoveItemDropDelegate: DropDelegate {
    let item: ImageMoveItem
    var vm:VM
    /// Drop finished work
    func performDrop(info: DropInfo) -> Bool {
        vm.draggingItem = nil
        vm.isDragging = false
        return true
    }
    /// Moving style without "+" icon
    func dropUpdated(info: DropInfo) -> DropProposal? {
        return DropProposal(operation: .move)
    }
    /// Object is dragged off of the onDrop view.
    func dropExited(info: DropInfo) {
        vm.isDragging = false
    }
    /// Object is dragged over the onDrop view.
    func dropEntered(info: DropInfo) {
        vm.isDragging = true
        guard let dragItem = vm.draggingItem, dragItem != item,
              let from = vm.images.firstIndex(of: dragItem),
              let to = vm.images.firstIndex(of: item) else {return}
        vm.images.move(fromOffsets: IndexSet(integer: from), toOffset: to > from ? to + 1 : to)
    }
}

class NSDelItemProvider: NSItemProvider {
    var didEnd: (() -> Void)?
    deinit {
        didEnd?()     // << here !!
    }
}
struct ImageDelItemDropDelegate: DropDelegate {
//    let item: ImageMoveItem
    var vm:VM
    /// Drop finished work
    func performDrop(info: DropInfo) -> Bool {
        if let dragItem = vm.draggingItem,
           let from = vm.images.firstIndex(of: dragItem) {
            vm.images.remove(at: from)
            if(!vm.images.contains(addImageMoveItem)){
                vm.images.append(addImageMoveItem)
            }
        }
        vm.draggingItem = nil
        vm.isDraggingInDelView = false
        return true
    }
    /// Moving style without "+" icon
    func dropUpdated(info: DropInfo) -> DropProposal? {
        return DropProposal(operation: .move)
    }
    /// Object is dragged off of the onDrop view.
    func dropExited(info: DropInfo) {
//        vm.draggingItem = nil
        vm.isDraggingInDelView = false
    }
    /// Object is dragged over the onDrop view.
    func dropEntered(info: DropInfo) {
        vm.isDraggingInDelView = true
    }
}

struct ImageMoveItem: Identifiable, Hashable{//, Transferable
    var id = UUID()
    var uiImage: UIImage
    var hasAdd:Bool = false
//    static var transferRepresentation: some TransferRepresentation {
//        DataRepresentation(exportedContentType: .png) { value in
//            return value.uiImage.pngData()!
//        }
//    }
}

//struct ImageMoveItem: Identifiable, Hashable, Transferable, Codable {
//    var id = UUID()
//    var uiImage: UIImage
//    var hasAdd: Bool = false
//    enum CodingKeys: String, CodingKey {
//        case id
//        case uiImageData
//        case hasAdd
//    }
//    init(uiImage: UIImage, hasAdd: Bool = false) {
//        self.uiImage = uiImage
//        self.hasAdd = hasAdd
//    }
//    init(from decoder: Decoder) throws {
//        let container = try decoder.container(keyedBy: CodingKeys.self)
//        id = try container.decode(UUID.self, forKey: .id)
//        hasAdd = try container.decode(Bool.self, forKey: .hasAdd)
//        
//        let uiImageData = try container.decode(Data.self, forKey: .uiImageData)
//        guard let image = UIImage(data: uiImageData) else {
//            throw DecodingError.dataCorruptedError(forKey: .uiImageData,
//                                                   in: container,
//                                                   debugDescription: "UIImage data is not valid")
//        }
//        uiImage = image
//    }
//    func encode(to encoder: Encoder) throws {
//        var container = encoder.container(keyedBy: CodingKeys.self)
//        try container.encode(id, forKey: .id)
//        try container.encode(hasAdd, forKey: .hasAdd)
//        
//        guard let uiImageData = uiImage.pngData() else {
//            throw EncodingError.invalidValue(uiImage,
//                                             EncodingError.Context(codingPath: [],
//                                                                   debugDescription: "UIImage could not be encoded"))
//        }
//        try container.encode(uiImageData, forKey: .uiImageData)
//    }
//    static var transferRepresentation: some TransferRepresentation {
//        CodableRepresentation(contentType: .item)
//    }
//}

有拖动到底部删除

//
//  VM.swift
//  testSign
//
//  Created by 1 on 2024/5/14.
//

import Foundation
import UIKit
import SwiftUI
import JFHeroBrowser

let keyWindow = UIApplication.shared.connectedScenes
                        .map({ $0 as? UIWindowScene })
                        .compactMap({ $0 })
                        .first?.windows.first

let myAppRootVC : UIViewController? = keyWindow?.rootViewController

let addImageMoveItem:ImageMoveItem = ImageMoveItem(uiImage: UIImage(named: "ic_imgadd") ?? UIImage(systemName: "plus")!,hasAdd: true)
let testImageMoveItem:ImageMoveItem = ImageMoveItem(uiImage: UIImage(systemName: "move.3d")!)

class VM: ObservableObject{
//    @Published var isImagePickerPresented = false
    
    // 定义网格布局
    let gridLayout: [GridItem] = Array(repeating: .init(.flexible()), count: 3)
    
    var photoPicker = QSPhotoPicker()
    @Published var images:[ImageMoveItem] = [
        addImageMoveItem,
    ]
    var maxImages:Int = 9
    var getTrueImageNum:Int{
        get{
            var getTrueImageNum = 0
            for image in images {
                if(!image.hasAdd){
                    getTrueImageNum = getTrueImageNum+1
                }
            }
            return getTrueImageNum
        }
    }
    
    //拖拽
    @Published var draggingItem: ImageMoveItem?
    @Published var isDragging = false
    // 用于标识是否有项目被拖动到删除区域
    @Published var isDraggingInDelView: Bool = false

    //做个延迟处理
    @Published var isCanDraggingTarget: Bool = false
    
    init() {

    }

    func imageBrowserPresented(index:Int){
        let imageBrowser = QSImageBrowser()
        var list: [UIImage] = []
        for image in images {
            if(!image.hasAdd){
                list.append(image.uiImage)
            }
        }
        imageBrowser.uiimages = list
        imageBrowser.imageBrowserPresented(index: index)
    }
    
    func imagePickerPresented(){
        photoPicker.maxSelectCount = self.maxImages - self.getTrueImageNum
        photoPicker.selectImageBlock = { results in
            var tempsImages:[ImageMoveItem] = []
            for item in results{
                tempsImages.append(ImageMoveItem(uiImage: item))
            }
            let allImages = self.getTrueImageNum + tempsImages.count
            if(allImages == self.maxImages){//去除+
                self.images.removeLast()
                self.images.append(contentsOf: tempsImages)
            }else{
                self.images.insert(contentsOf: tempsImages, at: self.images.count-1)
            }
        }
        photoPicker.imagePickerPresented()
    }
}

//MARK: Drop Delegate
struct ImageMoveItemDropDelegate: DropDelegate {
    var item: ImageMoveItem
    var vm:VM
   
    /// Drop finished work
    func performDrop(info: DropInfo) -> Bool {
        vm.draggingItem = nil
        vm.isDragging = false
        vm.images = vm.images
        return true
    }
    /// Moving style without "+" icon
    func dropUpdated(info: DropInfo) -> DropProposal? {
        if(vm.isCanDraggingTarget){
            if let dragItem = vm.draggingItem, dragItem != item,
               let from = vm.images.firstIndex(of: dragItem),
               let to = vm.images.firstIndex(of: item){
                vm.images.move(fromOffsets: IndexSet(integer: from), toOffset: to > from ? to + 1 : to)
            }
        }
        return DropProposal(operation: .move)
    }
    /// Object is dragged off of the onDrop view.
    func dropExited(info: DropInfo) {
        vm.isDragging = false
    }
    /// Object is dragged over the onDrop view.
    func dropEntered(info: DropInfo) {
        vm.isDragging = true
        vm.isCanDraggingTarget = false
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5){
            vm.isCanDraggingTarget = true
        }
//        guard let dragItem = vm.draggingItem, dragItem != item,
//              let from = vm.images.firstIndex(of: dragItem),
//              let to = vm.images.firstIndex(of: item)
//        else {return}
//        vm.images.move(fromOffsets: IndexSet(integer: from), toOffset: to > from ? to + 1 : to)
    }
}

class NSDelItemProvider: NSItemProvider {
    var didEnd: (() -> Void)?
    deinit {
        didEnd?()     // << here !!
    }
}
struct ImageDelItemDropDelegate: DropDelegate {
//    let item: ImageMoveItem
    var vm:VM
    /// Drop finished work
    func performDrop(info: DropInfo) -> Bool {
        if let dragItem = vm.draggingItem,
           let from = vm.images.firstIndex(of: dragItem) {
            vm.images.remove(at: from)
            if(!vm.images.contains(addImageMoveItem)){
                vm.images.append(addImageMoveItem)
            }
        }
        vm.draggingItem = nil
        vm.isDraggingInDelView = false
        return true
    }
    /// Moving style without "+" icon
    func dropUpdated(info: DropInfo) -> DropProposal? {
        return DropProposal(operation: .move)
    }
    /// Object is dragged off of the onDrop view.
    func dropExited(info: DropInfo) {
//        vm.draggingItem = nil
        vm.isDraggingInDelView = false
    }
    /// Object is dragged over the onDrop view.
    func dropEntered(info: DropInfo) {
        vm.isDraggingInDelView = true
    }
}

struct ImageMoveItem: Identifiable, Hashable{//, Transferable
    var id = UUID()
    var uiImage: UIImage
    var hasAdd:Bool = false
//    static var transferRepresentation: some TransferRepresentation {
//        DataRepresentation(exportedContentType: .png) { value in
//            return value.uiImage.pngData()!
//        }
//    }
}

//struct ImageMoveItem: Identifiable, Hashable, Transferable, Codable {
//    var id = UUID()
//    var uiImage: UIImage
//    var hasAdd: Bool = false
//    enum CodingKeys: String, CodingKey {
//        case id
//        case uiImageData
//        case hasAdd
//    }
//    init(uiImage: UIImage, hasAdd: Bool = false) {
//        self.uiImage = uiImage
//        self.hasAdd = hasAdd
//    }
//    init(from decoder: Decoder) throws {
//        let container = try decoder.container(keyedBy: CodingKeys.self)
//        id = try container.decode(UUID.self, forKey: .id)
//        hasAdd = try container.decode(Bool.self, forKey: .hasAdd)
//        
//        let uiImageData = try container.decode(Data.self, forKey: .uiImageData)
//        guard let image = UIImage(data: uiImageData) else {
//            throw DecodingError.dataCorruptedError(forKey: .uiImageData,
//                                                   in: container,
//                                                   debugDescription: "UIImage data is not valid")
//        }
//        uiImage = image
//    }
//    func encode(to encoder: Encoder) throws {
//        var container = encoder.container(keyedBy: CodingKeys.self)
//        try container.encode(id, forKey: .id)
//        try container.encode(hasAdd, forKey: .hasAdd)
//        
//        guard let uiImageData = uiImage.pngData() else {
//            throw EncodingError.invalidValue(uiImage,
//                                             EncodingError.Context(codingPath: [],
//                                                                   debugDescription: "UIImage could not be encoded"))
//        }
//        try container.encode(uiImageData, forKey: .uiImageData)
//    }
//    static var transferRepresentation: some TransferRepresentation {
//        CodableRepresentation(contentType: .item)
//    }
//}

相关文章

网友评论

      本文标题:swiftui 九宫格拖动排序

      本文链接:https://www.haomeiwen.com/subject/mgmvfjtx.html