Unwanted animation when moving items in SwiftUI list

為{幸葍}努か 提交于 2020-07-06 11:54:31

问题


I have a SwiftUI List like in the example code below.

struct ContentView: View {
    @State var numbers = ["1", "2", "3"]
    @State var editMode = EditMode.inactive

    var body: some View {
        NavigationView {
            List {
                ForEach(numbers, id: \.self) { number in
                    Text(number)
                }
                .onMove {
                    self.numbers.move(fromOffsets: $0, toOffset: $1)
                }
            }
            .navigationBarItems(trailing: EditButton())
        }
    }
}

When I enter edit mode and move the item one position up the strange animation happens after I drop the item (see the gif below). It looks like the dragged item comes back to its original position and then moves to the destination again (with animation)

What's interesting it doesn't happen if you drag the item down the list or more than one position up.

I guess it's because the List performs animation when the items in the state get reordered even though they were already reordered on the view side by drag and drop. But apparently it handles it well in all the cases other than moving item one position up.

Any ideas on how to solve this issue? Or maybe it's a known bug?

I'm using XCode 11.4.1 and the build target is iOS 13.4

(Please also note that in the "real world" app I'm using Core Data and when moving items their order is updated in the DB and then the state is updated, but the problem with the animation looks exactly the same.)


回答1:


Here is solution (tested with Xcode 11.4 / iOS 13.4)

var body: some View {
    NavigationView {
        List {
            ForEach(numbers, id: \.self) { number in
                HStack {
                    Text(number)
                }.id(UUID())        // << here !!
            }
            .onMove {
                self.numbers.move(fromOffsets: $0, toOffset: $1)
            }
        }
        .navigationBarItems(trailing: EditButton())
    }
}



回答2:


I have same problem. I do not know bug or not, but I found workaround solution.

class Number: ObservableObject, Identifiable {
    var id = UUID()
    @Published var number: String

    init(_ number: String) {
        self.number = number
    }
}

class ObservedNumbers: ObservableObject {
    @Published var numbers = [ Number("1"), Number("2"), Number("3") ]

    func onMove(fromOffsets: IndexSet, toOffset: Int) {
        var newNumbers = numbers.map { $0.number }
        newNumbers.move(fromOffsets: fromOffsets, toOffset: toOffset)

        for (newNumber, number) in zip(newNumbers, numbers) {
            number.number = newNumber
        }

        self.objectWillChange.send()
    }
}

struct ContentView: View {
    @ObservedObject var observedNumbers = ObservedNumbers()
    @State var editMode = EditMode.inactive

    var body: some View {
        NavigationView {
            List {
                ForEach(observedNumbers.numbers) { number in
                    Text(number.number)
                }
                .onMove(perform: observedNumbers.onMove)
            }
            .navigationBarItems(trailing: EditButton())
        }
    }
}

In CoreData case I just use NSFetchedResultsController. Implementation of ObservedNumbers.onMove() method looks like:

        guard var hosts = frc.fetchedObjects else {
            return
        }

        hosts.move(fromOffsets: set, toOffset: to)
        for (order, host) in hosts.enumerated() {
            host.orderPosition = Int32(order)
        }

        try? viewContext.save()

And in delegate:

    internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any,
                             at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
    {
        switch type {
        case .delete:
            hosts[indexPath!.row].stopTrack()
            hosts.remove(at: indexPath!.row)
        case .insert:
            let hostViewModel = HostViewModel(host: frc.object(at: newIndexPath!))
            hosts.insert(hostViewModel, at: newIndexPath!.row)
            hostViewModel.startTrack()
        case .update:
            hosts[indexPath!.row].update(host: frc.object(at: indexPath!))
        case .move:
            hosts[indexPath!.row].update(host: frc.object(at: indexPath!))
            hosts[newIndexPath!.row].update(host: frc.object(at: newIndexPath!))
        default:
            return
        }
    }



回答3:


Here's a solution based on Mateusz K's comment in the accepted answer. I combined the hashing of order and number. I'm using a complex object in place of number which gets dynamically updated. This way ensures the list item refreshes if the underlying object changes.

class HashNumber : Hashable{
        
        var order : Int
        var number : String
        
        init(_ order: Int, _ number:String){
            self.order = order
            self.number = number
        }
        
        static func == (lhs: HashNumber, rhs: HashNumber) -> Bool {
            return lhs.number == rhs.number && lhs.order == rhs.order
        }
        //
        func hash(into hasher: inout Hasher) {
            hasher.combine(number)
            hasher.combine(order)
        }
    }
    
    func createHashList(_ input : [String]) -> [HashNumber]{
        
        var r : [HashNumber] = []
        
        var order = 0
        for i in input{
            let h = HashNumber(order, i)
            r.append(h)
            order += 1
        }
        
        return r
    }
    
    struct ContentView: View {
        @State var numbers = ["1", "2", "3"]
        @State var editMode = EditMode.inactive
        
        var body: some View {
            NavigationView {
                List {
                    ForEach(createHashList(numbers), id: \.self) { number in
                        Text(number.number)
                    }
                    .onMove {
                        self.numbers.move(fromOffsets: $0, toOffset: $1)
                    }
                }
                .navigationBarItems(trailing: EditButton())
            }
        }
    }


来源:https://stackoverflow.com/questions/61571422/unwanted-animation-when-moving-items-in-swiftui-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!