Skip to content

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

@Donny1995

Description

@Donny1995

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)
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions