Beneath are the 2 Views that ought to matter for this query. I am simply attempting to tug and drop a Meal from one place in a Record to a different (similar Record). The concept is to let the consumer modify the time of the precise Meal on their Meal Plan. I really feel like I’ve tried nearly every little thing, however the .onDrop modifier shouldn’t be getting referred to as.
Any assistance on this may be drastically appreciated! I’d like to know what’s stopping this from working.
Here’s a image of what the animation seems to be like after I start dragging:
import SwiftUI
import UniformTypeIdentifiers
struct CalendarDayListView2: View {
@State var viewModel: CalendarDayListViewModel2
@Binding var date: Date
@Binding var meals: [Meal]
@Binding var isLoading: Bool
@State personal var scrollToMeal: Meal?
@Setting(.sizeCategory) var sizeCategory
@State personal var timeZone: TimeZone = TimeZone.present
var plusButtonAction: ((Date) -> Void)?
var xButtonAction: ((Meal) -> Void)?
var menuButtonAction: ((Meal) -> Void)?
var physique: some View {
ScrollViewReader { proxy in
Record {
ForEach(hoursOfDay, id: .self) { hour in
if let meal = mealForHour(hour) {
CalendarListItemView(
meal: meal,
date: hour,
mode: viewModel.mode,
plusButtonAction: { addMealDate in
// Deal with plus button motion
self.plusButtonAction?(addMealDate)
},
xButtonAction: { meal in
// Deal with x button motion
self.xButtonAction?(meal)
}, menuButtonAction: { meal in
self.menuButtonAction?(meal)
}
)
.id(meal.id)
.swipeActions(edge: .trailing) {
Button(function: .damaging) {
deleteMeal(meal)
} label: {
Label("Delete", systemImage: "trash")
}
}
.onDrag {
NSItemProvider(object: meal.id as NSString)
}
.onDrop(of: [UTType.text.identifier], delegate: CalendarDropDelegate(meals: $meals, date: hour))
} else {
CalendarListItemView(
meal: nil,
date: hour,
mode: viewModel.mode,
plusButtonAction: { addMealDate in
// Deal with plus button motion
self.plusButtonAction?(addMealDate)
},
xButtonAction: { meal in
// Deal with x button motion
self.xButtonAction?(meal)
}, menuButtonAction: { meal in
self.menuButtonAction?(meal)
}
)
.onDrop(of: [UTType.plainText.identifier], delegate: CalendarDropDelegate(meals: $meals, date: hour))
}
}
.listRowSeparator(.hidden)
// Add additional padding for iPhone SE
if sizeCategory == .accessibilityExtraExtraExtraLarge {
Shade.clear.body(top: 200)
}
else {
Shade.clear.body(top: 450)
}
}
.body(width: UIScreen.predominant.bounds.width, top: 700)
.listStyle(PlainListStyle())
.onChange(of: self.meals) {
scrollToMeal = self.meals.first
}
.onChange(of: scrollToMeal) {
if let meal = scrollToMeal {
withAnimation {
proxy.scrollTo(meal.id, anchor: .prime)
}
}
}
.onChange(of: self.date) { oldValue, newValue in
// Meal Sharing mode
scrollToMeal = mealsForSelectedDate.first
}
.onChange(of: self.isLoading) { oldValue, newValue in
if !newValue {
DispatchQueue.predominant.asyncAfter(deadline: .now() + 0.5) {
scrollToMeal = mealsForSelectedDate.first
}
}
}
}
}
personal func deleteMeal(_ meal: Meal) {
if let index = meals.firstIndex(the place: { $0.id == meal.id }) {
meals.take away(at: index)
}
}
personal func dateForHour(_ hour: Int) -> Date {
let calendar = Calendar.present
let startOfDay = calendar.startOfDay(for: date)
return calendar.date(byAdding: .hour, worth: hour, to: startOfDay)!
}
personal var hoursOfDay: [Date] {
let calendar = Calendar.present
var calendarWithTimeZone = calendar
calendarWithTimeZone.timeZone = timeZone
let startOfDay = calendarWithTimeZone.startOfDay(for: date)
return (0..<24).map { hour in
calendarWithTimeZone.date(byAdding: .hour, worth: hour, to: startOfDay)!
}
}
personal var mealsForSelectedDate: [Meal] {
self.meals.filter { meal in
guard let plannedAt = meal.plannedAt else { return false }
return Calendar.present.isDate(plannedAt, inSameDayAs: date)
}
}
personal func mealForHour(_ hour: Date) -> Meal? {
mealsForSelectedDate.first { meal in
guard var plannedAt = meal.plannedAt else { return false }
let calendar = Calendar.present
var calendarWithTimeZone = calendar
calendarWithTimeZone.timeZone = timeZone
// Convert plannedAt to the present timezone
plannedAt = plannedAt.convertToTimeZone(timeZone)
return calendarWithTimeZone.isDate(plannedAt, equalTo: hour, toGranularity: .hour)
}
}
}
struct CalendarDropDelegate: DropDelegate {
@Binding var meals: [Meal]
let date: Date
func performDrop(information: DropInfo) -> Bool {
guard let itemProvider = information.itemProviders(for: [.text]).first else { return false }
itemProvider.loadObject(ofClass: NSString.self) { (id, error) in
if let id = id as? String,
let index = meals.firstIndex(the place: { $0.id == id }) {
DispatchQueue.predominant.async {
var updatedMeal = meals[index]
print("performDrop: UpdatedMeal date: (date)")
updatedMeal.plannedAt = date
meals[index] = updatedMeal
}
}
}
return true
}
}
// CalendarListItemView.swift
struct CalendarListItemView: View {
var meal: Meal?
var date: Date
var mode: CalendarViewMode
@State var isSelected: Bool = false
var plusButtonAction: ((Date) -> Void)?
var xButtonAction: ((Meal) -> Void)?
var menuButtonAction: ((Meal) -> Void)?
var foodImageNotDisplayed = false
var hourString: String {
let formatter = DateFormatter()
// Set the locale to make sure AM/PM works accurately in all locales
formatter.locale = Locale(identifier: "en_US_POSIX")
// Set the specified format
formatter.dateFormat = "hh"
// Return the formatted date string
return formatter.string(from: self.date)
}
var meridianString: String {
let formatter = DateFormatter()
// Set the locale to make sure AM/PM works accurately in all locales
formatter.locale = Locale(identifier: "en_US_POSIX")
// Set the specified format
formatter.dateFormat = "a"
// Return the formatted date string
return formatter.string(from: self.date)
}
var physique: some View {
HStack {
ZStack {
RoundedRectangle(cornerRadius: 12.0)
.foregroundStyle(.black.opacity(0.03))
.body(width: 75, top: self.meal == nil ? 33.0 : 75.0 )
HStack {
Textual content(hourString)
.font(Font.customized("Open Sans", dimension: 16))
.foregroundColor(.black)
Textual content(meridianString)
.font(Font.customized("Open Sans", dimension: 16))
.foregroundColor(.black.opacity(0.5))
}
}
.padding(.main, 10)
if meal != nil {
HStack {
foodImage()
VStack {
if let meal = meal {
Textual content(meal.title)
.font(Font.customized("Open Sans", dimension: 15))
.foregroundColor(.black)
}
HStack {
if let energy = meal?.energy {
Textual content("(energy.formatDouble()) cals")
.font(Font.customized("Open Sans", dimension: 13))
.foregroundColor(Shade(crimson: 0.54, inexperienced: 0.72, blue: 0.13))
}
if let readyInMinutes = meal?.readyInMinutes {
Textual content("• (readyInMinutes) minutes")
.font(Font.customized("Open Sans", dimension: 13))
.foregroundColor(Shade(crimson: 0.54, inexperienced: 0.72, blue: 0.13))
}
}
}
}
}
Spacer()
if let meal = meal {
Button(motion: {
if self.mode == .mealSharing {
self.menuButtonAction?(meal)
}
else {
xButtonAction?(meal)
}
}, label: {
if self.mode == .mealSharing {
Picture("menu_button")
}
else {
Picture("x_button")
}
})
.padding(.trailing, 10)
}
// No meal for time slot
else {
Button(motion: {
self.isSelected.toggle()
if self.mode == .timeSlot {
self.plusButtonAction?(date)
}
}, label: {
if self.mode == .timeSlot && self.isSelected {
Picture("green_checkmark")
}
else if self.mode == .mealSharing {
EmptyView()
}
else {
Picture("plus_button")
}
})
.padding(.trailing, 10)
}
}
.body(top: self.meal == nil ? 50.0 : 75.0)
}
@ViewBuilder
func foodImage() -> some View {
if let imageUrl = meal?.picture, let url = URL(string: imageUrl) {
AsyncImage(url: url) { section in
if let picture = section.picture {
picture
.resizable()
.aspectRatio(contentMode: .fill)
.body(width: 75, top: 75)
.cornerRadius(10)
} else if section.error != nil {
Picture("placeholder_food_image")
.resizable()
.scaledToFill()
.body(width: 75, top: 75)
.cornerRadius(10)
.clipped()
} else {
Picture("placeholder_food_image")
.resizable()
.scaledToFill()
.body(width: 75, top: 75)
.cornerRadius(10)
.clipped()
}
}
.body(width: 75, top: 75)
} else {
Picture("placeholder_food_image")
.resizable()
.scaledToFill()
.body(width: 75, top: 75)
.cornerRadius(10)
.clipped()
}
}
}
I attempted the .onMove modifier and that didn’t work as properly. I attempted a number of completely different UTTypes for the .onDrop and that didn’t work. The loopy factor is I even examined making the ForEach merely iterate and show Textual content views of the hour and it nonetheless wouldn’t let me drop them.