Reputation: 11
I am currently working on implementing drag and drop functionality for a view similar to how iOS widgets behave on the home screen. I have grouped views with a size of 1x1 so that, when they are close to each other and dragged, they move as a single block. However, after grouping them, the animations are no longer smooth, or the animations seem to be missing compared to before.
import SwiftUI
import UniformTypeIdentifiers
struct Card: Identifiable, Equatable {
let id = UUID()
let title: String
let style: LayoutStyle
let height: CGFloat
static func ==(lhs: Card, rhs: Card) -> Bool {
return lhs.id == rhs.id
}
}
struct CardGroup: Identifiable {
let id = UUID()
let cards: [Card]
}
struct CardView: View {
let title: String
@Binding var showEdit: Bool
var body: some View {
ZStack(alignment: .topTrailing) {
ZStack(alignment: .center) {
RoundedRectangle(cornerRadius: 12)
.foregroundColor(.gray)
Text(title)
.font(.title2)
}
if showEdit {
Text("Edit")
}
}
}
}
struct MockStore {
static func cards() -> [Card] {
[
Card(title: "Small Card 1", style: .widget1x1, height: 1/1),
Card(title: "Small Card 2", style: .widget1x1, height: 1/1),
Card(title: "Small Card 3", style: .widget1x1, height: 1/1),
Card(title: "Regular2x1", style: .widget2x1, height: 2/1),
Card(title: "Small Card 4", style: .widget1x1, height: 1/1),
Card(title: "Large 3x2", style: .widget2x3, height: 2/3),
Card(title: "Large 2x2", style: .widget2x2, height: 2/2),
Card(title: "Small Card 5", style: .widget1x1, height: 1/1),
Card(title: "Small Card 6", style: .widget1x1, height: 1/1)
]
}
}
enum LayoutStyle {
case widget1x1
case widget2x1
case widget2x2
case widget2x3
}
struct ContentView: View {
@State private var cards: [Card] = MockStore.cards()
@State private var isEdit: Bool = false
@State private var draggedCard: Card?
@State private var offset: CGSize = .zero
var body: some View {
GeometryReader { geo in
ScrollView {
VStack(alignment: .leading) {
Button {
isEdit.toggle()
} label: {
Text("Edit")
}
ForEach(groupedCards(), id: \.id) { group in
LazyHStack {
ForEach(group.cards) { card in
CardView(title: card.title, showEdit: $isEdit)
.aspectRatio(card.height, contentMode: .fit)
.frame(width: card.style != .widget1x1 ? geo.size.width - 32 : (geo.size.width - 32) / 2)
.onDrag {
withAnimation(.default) {
self.draggedCard = card
return NSItemProvider()
}
}
.onDrop(of: [.text], delegate:
withAnimation(.default) {
DropViewDelegate(destinationItem: card, cards: $cards, draggedItem: $draggedCard)})
.background(self.draggedCard == card ? Color.clear : Color.clear)
.padding(.vertical, isEdit ? 16 : 0)
}
}.padding(.horizontal)
}
}
.transaction { transaction in
transaction.animation = nil
}
}
}
}
func groupedCards() -> [CardGroup] {
var result: [CardGroup] = []
var currentGroup: [Card] = []
for card in cards {
if card.style != .widget1x1 {
if !currentGroup.isEmpty {
result.append(CardGroup(cards: currentGroup))
currentGroup = []
}
result.append(CardGroup(cards: [card]))
} else {
if currentGroup.count == 1 {
if currentGroup.first?.style == .widget1x1 {
currentGroup.append(card)
result.append(CardGroup(cards: currentGroup))
currentGroup = []
} else {
result.append(CardGroup(cards: currentGroup))
currentGroup = [card]
}
} else {
currentGroup.append(card)
}
}
}
if !currentGroup.isEmpty {
result.append(CardGroup(cards: currentGroup))
}
return result
}
}
#Preview {
ContentView()
}
struct DropViewDelegate: DropDelegate {
let destinationItem: Card
@Binding var cards: [Card]
@Binding var draggedItem: Card?
func dropUpdated(info: DropInfo) -> DropProposal? {
return DropProposal(operation: .move)
}
func performDrop(info: DropInfo) -> Bool {
draggedItem = nil
return true
}
func dropEntered(info: DropInfo) {
if let draggedItem = draggedItem {
let fromIndex = cards.firstIndex(of: draggedItem)
if let fromIndex = fromIndex {
let toIndex = self.cards.firstIndex(of: destinationItem)
if let toIndex = toIndex, fromIndex != toIndex {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
withAnimation(.default) {
self.cards.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: (toIndex > fromIndex ? (toIndex + 1) : toIndex))
}
}
}
}
}
}
}
Could you help me figure out how to make the drag-and-drop animations as smooth as they were before grouping the 1x1-sized views into a single entity? Thank you very much!
Upvotes: 1
Views: 36