这适合没拖动到底部删除
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)
// }
//}