SwiftUI:有谁知道为什么我的 .onDrag 或 .onDrop 修饰符没有被调用?

问题描述 投票:0回答:1

以下是对于这个问题应该重要的两个视图。 我只是想将一份餐食从列表中的一个位置拖放到另一个位置(同一列表)。 这个想法是让用户调整其膳食计划中特定膳食的时间。 我觉得我已经尝试了几乎所有的方法,但是 .onDrop 修饰符没有被调用。

对此的任何帮助将不胜感激!我很想知道是什么阻止了它的工作。

这是我开始拖动时动画的样子:

Dragging Meal

import SwiftUI
import UniformTypeIdentifiers

struct CalendarDayListView2: View {
    
    @State var viewModel: CalendarDayListViewModel2
    @Binding var date: Date
    @Binding var meals: [Meal]
    @Binding var isLoading: Bool
    
    @State private var scrollToMeal: Meal?
    @Environment(\.sizeCategory) var sizeCategory
    @State private var timeZone: TimeZone = TimeZone.current

    var plusButtonAction: ((Date) -> Void)?
    var xButtonAction: ((Meal) -> Void)?
    var menuButtonAction: ((Meal) -> Void)?
    
    var body: some View {
        ScrollViewReader { proxy in
            List {
                ForEach(hoursOfDay, id: \.self) { hour in
                    if let meal = mealForHour(hour) {
                        CalendarListItemView(
                            meal: meal,
                            date: hour,
                            mode: viewModel.mode,
                            plusButtonAction: { addMealDate in
                                // Handle plus button action
                                self.plusButtonAction?(addMealDate)
                            },
                            xButtonAction: { meal in
                                // Handle x button action
                                self.xButtonAction?(meal)
                            }, menuButtonAction: { meal in
                                self.menuButtonAction?(meal)
                            }
                        )
                        .id(meal.id)
                        .swipeActions(edge: .trailing) {
                            Button(role: .destructive) {
                                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
                                // Handle plus button action
                                self.plusButtonAction?(addMealDate)
                            },
                            xButtonAction: { meal in
                                // Handle x button action
                                self.xButtonAction?(meal)
                            }, menuButtonAction: { meal in
                                self.menuButtonAction?(meal)
                            }
                        )
                        .onDrop(of: [UTType.plainText.identifier], delegate: CalendarDropDelegate(meals: $meals, date: hour))

                    }
                }
                .listRowSeparator(.hidden)
                
                // Add extra padding for iPhone SE
                if sizeCategory == .accessibilityExtraExtraExtraLarge {
                    Color.clear.frame(height: 200)
                }
                else {
                    Color.clear.frame(height: 450)
                }
            }
            .frame(width: UIScreen.main.bounds.width, height: 700)
            .listStyle(PlainListStyle())
            .onChange(of: self.meals) {
                
                scrollToMeal = self.meals.first
            }
            .onChange(of: scrollToMeal) {
                if let meal = scrollToMeal {
                    withAnimation {
                        proxy.scrollTo(meal.id, anchor: .top)
                    }
                }
            }
            .onChange(of: self.date) { oldValue, newValue in
                
                // Meal Sharing mode
                scrollToMeal = mealsForSelectedDate.first
            }
            .onChange(of: self.isLoading) { oldValue, newValue in
                if !newValue {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                        scrollToMeal = mealsForSelectedDate.first
                    }
                }
            }
        }
        
    }
    
    private func deleteMeal(_ meal: Meal) {
        if let index = meals.firstIndex(where: { $0.id == meal.id }) {
            meals.remove(at: index)
        }
    }
    
    private func dateForHour(_ hour: Int) -> Date {
        let calendar = Calendar.current
        let startOfDay = calendar.startOfDay(for: date)
        return calendar.date(byAdding: .hour, value: hour, to: startOfDay)!
    }
    
    private var hoursOfDay: [Date] {
        let calendar = Calendar.current
        var calendarWithTimeZone = calendar
        calendarWithTimeZone.timeZone = timeZone
        let startOfDay = calendarWithTimeZone.startOfDay(for: date)
        return (0..<24).map { hour in
            calendarWithTimeZone.date(byAdding: .hour, value: hour, to: startOfDay)!
        }
    }
    
    private var mealsForSelectedDate: [Meal] {
        self.meals.filter { meal in
            guard let plannedAt = meal.plannedAt else { return false }
            return Calendar.current.isDate(plannedAt, inSameDayAs: date)
        }
    }
    
    private func mealForHour(_ hour: Date) -> Meal? {
        mealsForSelectedDate.first { meal in
            guard var plannedAt = meal.plannedAt else { return false }
            let calendar = Calendar.current
            var calendarWithTimeZone = calendar
            calendarWithTimeZone.timeZone = timeZone
            
            // Convert plannedAt to the current 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(info: DropInfo) -> Bool {
        guard let itemProvider = info.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(where: { $0.id == id }) {
                DispatchQueue.main.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 ensure AM/PM works correctly in all locales
        formatter.locale = Locale(identifier: "en_US_POSIX")
        // Set the desired 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 ensure AM/PM works correctly in all locales
        formatter.locale = Locale(identifier: "en_US_POSIX")
        // Set the desired format
        formatter.dateFormat = "a"
        
        // Return the formatted date string
        return formatter.string(from: self.date)
    }
            
    var body: some View {
        HStack {
            ZStack {
                RoundedRectangle(cornerRadius: 12.0)
                    .foregroundStyle(.black.opacity(0.03))
                    .frame(width: 75, height: self.meal == nil ? 33.0 : 75.0 )
                HStack {
                    Text(hourString)
                        .font(Font.custom("Open Sans", size: 16))
                        .foregroundColor(.black)
                    Text(meridianString)
                        .font(Font.custom("Open Sans", size: 16))
                        .foregroundColor(.black.opacity(0.5))
                }
            }
            .padding(.leading, 10)
            
            if meal != nil {
                
                HStack {
                    
                    foodImage()
                    
                    VStack {
                        if let meal = meal {
                            
                            Text(meal.title)
                                .font(Font.custom("Open Sans", size: 15))
                                .foregroundColor(.black)
                        }
                        HStack {
                            if let calories = meal?.calories {
                                Text("\(calories.formatDouble()) cals")
                                    .font(Font.custom("Open Sans", size: 13))
                                    .foregroundColor(Color(red: 0.54, green: 0.72, blue: 0.13))
                            }

                            if let readyInMinutes = meal?.readyInMinutes {
                                Text("• \(readyInMinutes) minutes")
                                    .font(Font.custom("Open Sans", size: 13))
                                    .foregroundColor(Color(red: 0.54, green: 0.72, blue: 0.13))
                            }

                        }
                    }
                    
                }
            }
            
            Spacer()
            
            if let meal = meal {
                Button(action: {
                    if self.mode == .mealSharing {
                        self.menuButtonAction?(meal)
                    }
                    else {
                        xButtonAction?(meal)
                    }
                }, label: {
                    if self.mode == .mealSharing {
                        Image("menu_button")
                    }
                    else {
                        Image("x_button")
                    }
                })
                .padding(.trailing, 10)
            }
            // No meal for time slot
            else {
                
                Button(action: {
                    self.isSelected.toggle()
                    if self.mode == .timeSlot {
                        self.plusButtonAction?(date)
                    }
                    
                }, label: {
                    if self.mode == .timeSlot && self.isSelected {
                        Image("green_checkmark")
                    }
                    else if self.mode == .mealSharing {
                        EmptyView()
                    }
                    else {
                        Image("plus_button")
                    }
                })
                .padding(.trailing, 10)
            }
        }
        .frame(height: self.meal == nil ? 50.0 : 75.0)
    }
        
    @ViewBuilder
    func foodImage() -> some View {
        if let imageUrl = meal?.image, let url = URL(string: imageUrl) {
            AsyncImage(url: url) { phase in
                if let image = phase.image {
                    image
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(width: 75, height: 75)
                        .cornerRadius(10)
                } else if phase.error != nil {
                    Image("placeholder_food_image")
                        .resizable()
                        .scaledToFill()
                        .frame(width: 75, height: 75)
                        .cornerRadius(10)
                        .clipped()
                } else {
                    Image("placeholder_food_image")
                        .resizable()
                        .scaledToFill()
                        .frame(width: 75, height: 75)
                        .cornerRadius(10)
                        .clipped()
                }
            }
            .frame(width: 75, height: 75)
        } else {
            Image("placeholder_food_image")
                .resizable()
                .scaledToFill()
                .frame(width: 75, height: 75)
                .cornerRadius(10)
                .clipped()
        }
    }
}

我尝试了 .onMove 修饰符,但效果不佳。 我为 .onDrop 尝试了多种不同的 UTType,但没有成功。 疯狂的是,我什至测试了让 ForEach 简单地迭代并显示一小时的文本视图,但它仍然不让我放弃它们。

ios swift swiftui
1个回答
0
投票

您不必使用

onDrag
/
onDrop
来重新排序
List
中的商品。

以下是如何实现这一目标的简化示例:

List(selection: $selected) {
    ForEach($items, id: \.id) { item in
        ItemView(item)
            .tag(item.id.wrappedValue)
    }
    .onMove { indices, newOffset in
        items.move(fromOffsets: indices, toOffset: newOffset)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.