Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reordering section and rows ends up in a total mess #85

Open
Donny1995 opened this issue Mar 12, 2022 · 2 comments
Open

Reordering section and rows ends up in a total mess #85

Donny1995 opened this issue Mar 12, 2022 · 2 comments

Comments

@Donny1995
Copy link

Donny1995 commented Mar 12, 2022

I have performed the following:

data before:

[
    TestData(id: "SECTION_0", rows: [
        "S0_R0",
        "S0_R1",
        "S0_R2",
        "S0_R3",
        "S0_R4",
        "S0_R5"
    ]),
    TestData(id: "SECTION_1", rows: [
        "S1_R0",
        "S1_R1"
    ]),
]

data after:

[
    TestData(id: "SECTION_1", rows: [
        "S1_R0",
        "S1_R1"
    ]),
    TestData(id: "SECTION_0", rows: [
        "S0_R0",
        "S0_R4", // <-
        "S0_R1",
        "S0_R2",
        "S0_R3", // ->
        "S0_R5"
    ]),
]

We move 1 section up,
and we move 1 row up
As a result i see a total mess of rows and sections
What am i doing wrong?

This behavior is easily reproduced by the following code:

import Foundation
import UIKit

struct TestData: CollectionDecorator {
    let id: String
    let rows: [String]
    
    typealias InnerCollectionType = [String]
    var collection: [String] { rows }
}

class TestController2: UIViewController {
    
    let mTableView = UITableView()
    
    var data: [TestData] = [
        TestData(id: "SECTION_0", rows: [
            "S0_R0",
            "S0_R1",
            "S0_R2",
            "S0_R3",
            "S0_R4",
            "S0_R5"
        ]),
        TestData(id: "SECTION_1", rows: [
            "S1_R0",
            "S1_R1"
        ]),
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        addSubview(mTableView)
        mTableView.translatesAutoresizingMaskIntoConstraints = false
        mTableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        mTableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        mTableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        mTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        mTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        mTableView.dataSource = self
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
            guard let self = self else { return }
            self.recursiveShuffle()
        }
    }
    
    func recursiveShuffle() {
        
        let newData = [
            TestData(id: "SECTION_1", rows: [
                "S1_R0",
                "S1_R1"
            ]),
            TestData(id: "SECTION_0", rows: [
                "S0_R0",
                "S0_R4", // <-
                "S0_R1",
                "S0_R2",
                "S0_R3", // ->
                "S0_R5"
            ]),
        ]
        
        updateTable(newData: newData) {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
                guard let self = self else { return }
                
                let newDataBack = [
                    TestData(id: "SECTION_0", rows: [
                        "S0_R0",
                        "S0_R1", //->
                        "S0_R2",
                        "S0_R3",
                        "S0_R4", //<-
                        "S0_R5"
                    ]),
                    TestData(id: "SECTION_1", rows: [
                        "S1_R0",
                        "S1_R1"
                    ]),
                ]
                
                self.updateTable(newData: newDataBack) {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
                        guard let self = self else { return }
                        self.recursiveShuffle()
                    }
                }
            }
        }
    }
}

extension TestController2: UITableViewDataSource {
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return data.count
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data[section].count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        let text = data[indexPath.section][indexPath.row]
        cell.textLabel?.text = text
        
        if text.hasPrefix("S0") {
            cell.backgroundColor = .green
        } else {
            cell.backgroundColor = .red
        }
        
        return cell
    }
    
    func updateTable(newData: [TestData], completion: @escaping () -> Void) {
        
        let diff = data.nestedExtendedDiff(to: newData, isEqualSection: { $0.id == $1.id }, isEqualElement: { $0 == $1 })
        
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        data = newData
        mTableView.apply(diff, indexPathTransform: { $0 }, sectionTransform: { $0 })
        CATransaction.commit()
    }
}

/// Simply need to make something a collection is easy, if it has a collection inside
protocol CollectionDecorator: Collection {
    associatedtype InnerCollectionType: Collection
    var collection: InnerCollectionType { get }
}

extension CollectionDecorator {
    
    typealias Index = InnerCollectionType.Index
    typealias Element = InnerCollectionType.Element
    typealias Iterator = InnerCollectionType.Iterator
    typealias SubSequence = InnerCollectionType.SubSequence
    typealias Indices = InnerCollectionType.Indices
    
    func makeIterator() -> InnerCollectionType.Iterator { collection.makeIterator() }
    var underestimatedCount: Int { collection.underestimatedCount }
    func withContiguousStorageIfAvailable<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R? {
        try collection.withContiguousStorageIfAvailable(body)
    }
    
    var startIndex: Self.Index { collection.startIndex }
    var endIndex: Self.Index { collection.endIndex }
    
    
    
    subscript(position: Self.Index) -> Self.Element {
        return collection[position]
    }
    
    subscript(bounds: Range<Self.Index>) -> Self.SubSequence {
        return collection[bounds]
    }
    
    var indices: Self.Indices { return collection.indices }
    var isEmpty: Bool { return collection.isEmpty }
    var count: Int { return collection.count }
    
    func index(_ i: Self.Index, offsetBy distance: Int) -> Self.Index {
        return collection.index(i, offsetBy: distance)
    }
    
    func index(_ i: Self.Index, offsetBy distance: Int, limitedBy limit: Self.Index) -> Self.Index? {
        return collection.index(i, offsetBy: distance, limitedBy: limit)
    }
    
    func distance(from start: Self.Index, to end: Self.Index) -> Int {
        return collection.distance(from: start, to: end)
    }
    
    func index(after i: Self.Index) -> Self.Index {
        collection.index(after: i)
    }
    
    func formIndex(after i: inout Self.Index) {
        collection.formIndex(after: &i)
    }
}
@tonyarnold
Copy link
Owner

How are you providing equality for these types? Based on the id property?

@Donny1995
Copy link
Author

How are you providing equality for these types? Based on the id property?

Yeah, by 'id'
My active hypothesis for this is: "There can be only one change per item", it is true for rows, and for sections
So, if someone moves a section, maybe the number of items in it must stay the same?

But in my implementation of table animator i disabled the actual moving of sections, because it messed things up the same way: (rows appeared where they did not belong, and after some iterations table crashes)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants