Skip to content

Instantly share code, notes, and snippets.

@byaruhaf
Last active November 3, 2023 23:19
Show Gist options
  • Save byaruhaf/9f06ff1d058bfccc06539dae7f9a51db to your computer and use it in GitHub Desktop.
Save byaruhaf/9f06ff1d058bfccc06539dae7f9a51db to your computer and use it in GitHub Desktop.
PhotosGridViewDraggableX
//
// ContentView.swift
// TestDrag
//
// Created by Franklin Byaruhanga on 23/09/2023.
//
import SwiftUI
struct ColorItemView: View {
let url: URL
let cellSize: CGSize
var body: some View {
AsyncImage(url: url) { phase in
switch phase {
case .success(let image):
image
.resizable()
.scaledToFill()
default:
ProgressView()
}
}
.cornerRadius(15)
.frame(width: cellSize.width, height: cellSize.height)
.clipped()
}
}
struct DropViewDelegate: DropDelegate {
let destinationItem: URL
@Binding var colors: [URL]
@Binding var draggedItem: URL?
@Binding var lastUpdated: Date? // this is new!
@Binding var isDrop: Bool
func dropExited(info: DropInfo) {
self.isDrop = false
}
func dropUpdated(info: DropInfo) -> DropProposal? {
DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
draggedItem = nil
return true
}
func dropEntered(info: DropInfo) {
self.isDrop = true
// Swap Items
if let draggedItem {
let fromIndex = colors.firstIndex(of: draggedItem)
if let fromIndex {
let toIndex = colors.firstIndex(of: destinationItem)
if let toIndex, fromIndex != toIndex {
let now: Date = .now
if (lastUpdated == nil || now.timeIntervalSince(lastUpdated!) > 0.2) {
lastUpdated = .now
withAnimation {
self.colors.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: (toIndex > fromIndex ? (toIndex + 1) : toIndex))
}
}
}
}
}
}
}
struct PhotosGridViewDraggableX: View {
@State private var draggedColor: URL?
@State var colors: [URL] = [
URL(string: "https://picsum.photos/id/1/500/500")!,
URL(string: "https://picsum.photos/id/20/500/500")!,
URL(string: "https://picsum.photos/id/50/500/500")!,
URL(string: "https://picsum.photos/id/100/500/500")!,
URL(string: "https://picsum.photos/id/67/500/500")!,
URL(string: "https://picsum.photos/id/6/500/500")!
]
@State private var lastUpdated: Date? // this is new!
@State var isDrop: Bool = false
var body: some View {
GeometryReader { proxy in
let hGap: CGFloat = 14
let width = proxy.size.width - (hGap * 2)
let cellWidth = (width / 3)
let cellSize = CGSize(width: cellWidth, height: cellWidth * 1.5)
let columns = [GridItem(.fixed(cellWidth)), GridItem(.fixed(cellWidth)), GridItem(.fixed(cellWidth))]
LazyVGrid(columns: columns) {
ForEach(colors, id: \.self) { color in
ColorItemView(url: color, cellSize: cellSize)
.overlay(
(isDrop && (color == draggedColor))
? Rectangle().fill(.thinMaterial)
: nil
)
// .opacity( (isDrop && (color == draggedColor)) ? 0.01 : 1)
.onDrag {
self.draggedColor = color
return NSItemProvider()
} preview: {
ColorItemView(url: color, cellSize: cellSize)
.opacity(1)
}
.onDrop(of: [.text],
delegate: DropViewDelegate(destinationItem: color, colors: $colors, draggedItem: $draggedColor, lastUpdated: $lastUpdated, isDrop: $isDrop)
)
}
}
}
}
}
#Preview {
PhotosGridViewDraggableX()
}
@solkpolk
Copy link

solkpolk commented Nov 3, 2023

I used this:

//
//  ContentView.swift
//  testing
//
//  Created by Vybe Together on 11/3/23.
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
			PhotosGridViewDraggableX()
        }
        .padding()
    }
}


struct ColorItemView: View {
	let url: URL
	let cellSize: CGSize
	
	var body: some View {
		AsyncImage(url: url) { phase in
			switch phase {
				case .success(let image):
					image
						.resizable()
						.scaledToFill()
					
				default:
					ProgressView()
			}
		}
		.cornerRadius(15)
		.frame(width: cellSize.width, height: cellSize.height)
		.clipped()
	}
}

struct DropViewDelegate: DropDelegate {
	
	let destinationItem: URL
	@Binding var colors: [URL]
	@Binding var draggedItem: URL?
	@Binding var lastUpdated: Date? // this is new!
	@Binding var isDrop: Bool
	
	func dropExited(info: DropInfo) {
		self.isDrop = false
	}
	
	func dropUpdated(info: DropInfo) -> DropProposal? {
		DropProposal(operation: .move)
	}
	
	func performDrop(info: DropInfo) -> Bool {
		draggedItem = nil
		return true
	}
	
	func dropEntered(info: DropInfo) {
		self.isDrop = true
		// Swap Items
		if let draggedItem {
			let fromIndex = colors.firstIndex(of: draggedItem)
			if let fromIndex {
				let toIndex = colors.firstIndex(of: destinationItem)
				if let toIndex, fromIndex != toIndex {
					let now: Date = .now
					if (lastUpdated == nil || now.timeIntervalSince(lastUpdated!) > 0.2) {
						lastUpdated = .now
						withAnimation {
							self.colors.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: (toIndex > fromIndex ? (toIndex + 1) : toIndex))
						}
					}
				}
			}
		}
	}
}

struct PhotosGridViewDraggableX: View {
	
	@State private var draggedColor: URL?
	@State var colors: [URL] = [
		URL(string: "https://picsum.photos/id/1/500/500")!,
		URL(string: "https://picsum.photos/id/20/500/500")!,
		URL(string: "https://picsum.photos/id/50/500/500")!,
		URL(string: "https://picsum.photos/id/100/500/500")!,
		URL(string: "https://picsum.photos/id/67/500/500")!,
		URL(string: "https://picsum.photos/id/6/500/500")!
	]
	@State private var lastUpdated: Date? // this is new!
	@State var isDrop: Bool = false
	
	var body: some View {
		
		GeometryReader { proxy in
			let hGap: CGFloat = 14
			let width = proxy.size.width - (hGap * 2)
			let cellWidth = (width / 3)
			let cellSize = CGSize(width: cellWidth, height: cellWidth * 1.5)
			
			let columns = [GridItem(.fixed(cellWidth)), GridItem(.fixed(cellWidth)), GridItem(.fixed(cellWidth))]
			
			LazyVGrid(columns: columns) {
				ForEach(colors, id: \.self) { color in
					ColorItemView(url: color, cellSize: cellSize)
//						.overlay(
//							(isDrop && (color == draggedColor)) ? Rectangle().fill(.thinMaterial): nil
//						)
						.opacity( (isDrop && (color == draggedColor)) ? 0.01 : 1)
						.onDrag {
							self.draggedColor = color
							return NSItemProvider()
						} preview: {
							ColorItemView(url: color, cellSize: cellSize)
								.opacity(1)
						}
						.onDrop(of: [.text],
								delegate: DropViewDelegate(destinationItem: color, colors: $colors, draggedItem: $draggedColor, lastUpdated: $lastUpdated, isDrop: $isDrop)
						)
				}
				
			}
		}
	}
}

this is the video see how the onDrag Preview does not always work

sample.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment