рд╣рдо рдЯреЗрдмрд▓ рдПрдбреЗрдкреНрдЯрд░ рдХреЗ рд╕рд╛рде рдХреИрд╕реЗ рдЖрдП рдФрд░ UITableView рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХреЛ рд╕рд░рд▓ рдмрдирд╛рдпрд╛


рд╕рд╛рде рдХрд╛рдо рдХрд░рддреЗ рд╕рдордп, UITableViewрдореИрдВ рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреЛрдб рд▓рд┐рдЦрдиреЗ рд╕реЗ рдмрдЪрдирд╛ рдЪрд╛рд╣рддрд╛ рдерд╛, рдЬреЛ рдХрд┐ рдФрд░ рднреА рдЬрдЯрд┐рд▓ рд╣реИ рдпрджрд┐ рдЖрдкрдХреЛ рддрд╛рд▓рд┐рдХрд╛ рдХреА рд╕реНрдерд┐рддрд┐ рдХреЛ рдПрдирд┐рдореЗрдЯреЗрдб рд░реВрдк рд╕реЗ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред Apple рдиреЗ WWDC 2019 рдореЗрдВ рдЗрд╕ рд╕рдорд╕реНрдпрд╛ рдХрд╛ рд╕рдорд╛рдзрд╛рди рдкреНрд░рд╕реНрддреБрдд рдХрд┐рдпрд╛, рд▓реЗрдХрд┐рди рдпрд╣ рдХреЗрд╡рд▓ iOS 13. рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ рдФрд░ рд╣рдо, рдПрдХ рдореЛрдмрд╛рдЗрд▓ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдбреЗрд╡рд▓рдкрдореЗрдВрдЯ рд╕реНрдЯреВрдбрд┐рдпреЛ рдХреЗ рд░реВрдк рдореЗрдВ, iOS рдХреЗ рдиреНрдпреВрдирддрдо рд╕рдВрд╕реНрдХрд░рдг рдХреЛ рдЪреБрдирдиреЗ рдХреА рд▓рдХреНрдЬрд░реА рдирд╣реАрдВ рд░рдЦрддреЗ рд╣реИрдВред


рдЗрд╕рд▓рд┐рдП, рд╣рдордиреЗ рд░рд╛рд╕реНрддреЗ рдореЗрдВ рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЗ рд╡рд┐рдиреНрдпрд╛рд╕ рдХреЛ рд╕рд░рд▓ рдХрд░рддреЗ рд╣реБрдП, рддрд╛рд▓рд┐рдХрд╛рдУрдВ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдбреЗрдЯрд╛-рд╕рдВрдЪрд╛рд▓рд┐рдд рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХреА рд╣рдорд╛рд░реА рджреГрд╖реНрдЯрд┐ рдХрд╛ рдПрд╣рд╕рд╛рд╕ рдХрд┐рдпрд╛ред рдФрд░ рдЙрдиреНрд╣реЛрдВрдиреЗ рдПрдХ рдПрдирд┐рдореЗрдЯреЗрдб рдЯреЗрдмрд▓ рдЕрдкрдбреЗрдЯ рдЬреЛрдбрд╝рд╛, рдЬреЛ рдХрд┐ рд░реИрдЦрд┐рдХ рд╕рдордп рдУ (рдПрди) рдХреЗ рд▓рд┐рдП рдкреБрд░рд╛рдиреЗ рдФрд░ рдирдП рдбреЗрдЯрд╛ рдХреЗ рдмреАрдЪ рдЕрдВрддрд░ рдХреА рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рдЧрдгрдирд╛ рдкрд░ рдЖрдзрд╛рд░рд┐рдд рд╣реИ ред рдпрд╣ рд╕рдм рд╣рдордиреЗ рдПрдХ рдЫреЛрдЯреЗ рд╕реЗ рдкреБрд╕реНрддрдХрд╛рд▓рдп рдореЗрдВ рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд┐рдпрд╛ рд╣реИ рдЬрд┐рд╕реЗ рдЯреЗрдмрд▓ рдПрдбреЗрдкреНрдЯрд░ рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИред


рд╣рдордиреЗ рдХреНрдпрд╛ рдХрд┐рдпрд╛ рдФрд░ рд╣рдо рдХреИрд╕реЗ рдЖрдП, рдЗрд╕ рдкрд░ рд▓реЗрдЦ рдореЗрдВ рдЪрд░реНрдЪрд╛ рдХреА рдЬрд╛рдПрдЧреАред


TableAdapter рдХреЗ рдкреЗрд╢реЗрд╡рд░реЛрдВ рдФрд░ рд╡рд┐рдкрдХреНрд╖


рдкреЗрд╢реЗрд╡рд░реЛрдВ


  • рдПрдиреАрдореЗрд╢рди рддрд╛рд▓рд┐рдХрд╛ рдЕрджреНрдпрддрди
  • рд░реИрдЦрд┐рдХ рд╕рдордп рдореЗрдВ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рдЕрдВрддрд░ рдЧрдгрдирд╛
  • рдХреЛрдИ рддрд╛рд▓рд┐рдХрд╛, рд╕реЗрд▓ рдпрд╛ рдореЙрдбрд▓ рдЗрдирд╣реЗрд░рд┐рдЯ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИ
  • рдЕрдм рдФрд░ рдирд╣реАрдВ dequeReusable...
  • рдкреНрд░рдХрд╛рд░-рд╕реБрд░рдХреНрд╖рд┐рдд рд╕реЗрд▓ рд╕реЗрдЯрдЕрдк, рд╣реЗрдбрд░ / рдкрд╛рдж
  • рд╕реЗрд▓ рдЖрд░рдВрднреАрдХрд░рдг рдХрд┐рд╕реА рднреА рддрд░рд╣ рд╕реЗ
  • рдЖрд╕рд╛рди рдЕрдиреБрднрд╛рдЧ рд╕реЗрдЯрдЕрдк
  • рд╡рд┐рд╕реНрддрд╛рд░ рдХрд░рдиреЗ рдореЗрдВ рдЖрд╕рд╛рди

minuses


, Hashable. , - Swift, .. . - .





тАУ :


1. , /


1.1


, , Hashable.


extension User: Hashable { ... }

1.2


Configurable, . , .


extension Cell: Configurable {

    public func setup(with item: User) {
        textLabel?.text = item.name
    }
}

, , , . , setup(with:) .


1.3 /


/ Configurable, . / 1.3 .


2.


() , /. Section<Item, SectionId>, (Item) (SectionId). (id) Hashable, .


/


/ header/footer.


/


/ headerIdentifier/footerIdentifier, . , Configurable ( 1.3).


let section = Section<User, Int>(
    id: 0,
    items: users,
    header: "Users Section",
    footer: "Users Section",
    headerIdentifier: "HeaderReuseIdentifier",
    footerIdentifier: "FooterReuseIdentifier"
)

id .

3.


TableAdapter<Item, SectionId>, (Item) (SectionId). , .


CellReuseIdentifierProvider, . , IndexPath , .


ellDidSelectHandler, , IndexPath .


lazy var adapter = TableAdapter<User, Int>(
    tableView: tableView,
    cellIdentifierProvider: { (indexPath, item) -> String? in
        // Return cell reuse identifier for item at indexPath
    },
    cellDidSelectHandler: { [weak self] (table, indexPath, item) in
        // Handle cell selection for item at indexPath
    }
)

:


adapter.update(with: [section], animated: true)

class ViewController: UIViewController {

    let tableView = ...

    lazy var adapter = TableAdapter<User, Int>(
        tableView: tableView,
        cellIdentifierProvider: { (indexPath, item) -> String? in
            return "CellReuseIdentifier"
        },
        cellDidSelectHandler: { [weak self] (table, indexPath, item) in
            // Handle cell selection for item at indexPath
        }
    )

    let users: [User] = [...]

    override func viewDidLoad() {
        super.viewDidLoad()

        setupTable()

        let section = Section<User, Int>(
            id: 0,
            items: users,
            header: "Users Section",
            footer: "Users Section",
            headerIdentifier: "HeaderReuseIdentifier",
            footerIdentifier: "FooterReuseIdentifier"
        )

        adapter.update(with: [section]], animated: true)
    }

    func setupTable() {
        tableView.register(
            Cell.self,
            forCellReuseIdentifier: "CellReuseIdentifier"
        )

        tableView.register(
            Header.self,
            forHeaderFooterViewReuseIdentifier identifier: "HeaderReuseIdentifier"
        )
        tableView.register(
            Footer.self,
            forHeaderFooterViewReuseIdentifier identifier: "FooterReuseIdentifier"
        )
    }
}

Hashable , (associated value).



, CellReuserIdentifierProvider. A , "Cell" :


tableView.register(
    Cell.self,
    forCellReuseIdentifier: adapter.defaultCellIdentifier
)

/


headerIdentifier/footerIdentifier . / , "Header"/"Footer".


tableView.register(
    HeaderView.self,
    forHeaderFooterViewReuseIdentifier identifier: adapter.defaultHeaderIdentifier
)
tableView.register(
    FooterView.self,
    forHeaderFooterViewReuseIdentifier identifier: adapter.defaultFooterIdentifier
)

Sender


, , , , TableAdapter'a sender. , / SenderConfigurable.



class ViewController: UIViewController {

    lazy var adapter = TableAdapter<User, Int>(
        tableView: tableView,
        sender: self
    )
}

extension Cell: SenderConfigurable {

    func setup(with item: User, sender: ViewController) {
        textLabel?.text = item.name
        delegate = sender
    }
}

: , . , setup(with:sender:) .



, CellProvider, :


lazy var adapter = TableAdapter<User, Int>(
    tableView: tableView,
    cellProvider: { (table, indexPath, user) in
        let cell = table.dequeueReusableCell(
            withIdentifier: "CellReuseIdentifier",
            for: indexPath
        ) as! Cell

        cell.setup(with: user)

        return cell
    }
)

/ TableAdapter .



(diff) , . , A technique for isolating differences between files. O(n) . Hashable.


, , performBatchUpdates(_:completion:) . . .



  1. () (id). .

let sectionsDiff = try calculateDiff(form: oldSections, to: newSections)

  1. , , . , , id. , , , . , , .

let intermediateSections = applyDiff(sectionsDiff, to: oldSections)

  1. ( id) . . , , .

let rowsDiff = try calculateRowsDiff(from: intermediateSections, to: newSections)

:


let diff = Diff(
    sections: sectionsDiff,
    rows: rowsDiff,
    intermediateData: intermediateSections,
    resultData: newSections
)


  1. : ( ) , .

data = diff.intermediateData

tableView.insertSections(diff.sections.inserts, with: animationType)
tableView.deleteSections(diff.sections.deletes, with: animationType)
diff.sections.moves.forEach { tableView.moveSection($0.from, toSection: $0.to) }

  1. : () , .

data = diff.resultData

tableView.deleteRows(at: diff.rows.deletes, with: animationType)
tableView.insertRows(at: diff.rows.inserts, with: animationType)
diff.rows.moves.forEach { tableView.moveRow(at: $0.from, to: $0.to) }

1: . ┬л┬╗ ┬л┬╗ .



2: , .. . , , , , . .. , . adapter.update(with: sections, animated: false) .


Configurable


, :


let user = users[indexPath.row]

let cell = tableView.dequeueReusableCell(
    withIdentifier: cellId,
    for: indexPath
) as! Cell

cell.setup(with: user)

. (associatedtype).


Configurable, :


protocol Configurable {

    associatedtype ItemType: Any
    func setup(with item: ItemType)
}

, dequeueReusableCell(withIdentifier:for:) Configurable - ItemType. AnyConfigurable:


protocol AnyConfigurable {

    func anySetup(with item: Any)
}

, :


let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)

if let cell = cell as? AnyConfigurable {
    cell.anySetup(with: item)
}

Configurable AnyConfigurable :


extension Configurable {

    func anySetup(with item: Any) {
        if let item = item as? ItemType {
            setup(with: item)
        }
    }
}

, - ┬л┬╗ , . , .


, Swift type-erased wrappers. AnyHashable, AnyIndex.


: . : .


GitHub


рдирддреАрдЬрддрди, рд╣рдореЗрдВ рдПрдХ рд╕рдорд╛рдзрд╛рди рдорд┐рд▓рд╛ рдЬреЛ рдЖрдкрдХреЛ рдЖрд╕рд╛рдиреА рд╕реЗ рдХреЛрд╢рд┐рдХрд╛рдУрдВ рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ, рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛ рдХрдо рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреЛрдб рдФрд░ рдПрдиреАрдореЗрд╢рди рд▓рд┐рдЦрддрд╛ рд╣реИред рдЙрд╕реА рд╕рдордп, рдирд┐рдореНрди-рд╕реНрддрд░реАрдп рддрд╛рд▓рд┐рдХрд╛ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдФрд░ рдпрджрд┐ рдЖрд╡рд╢реНрдпрдХ рд╣реЛ рддреЛ рдХрд╛рд░реНрдпрд╛рддреНрдордХ рд╡рд┐рд╕реНрддрд╛рд░ рдХреА рд╕рдВрднрд╛рд╡рдирд╛ рдмрдиреА рд░рд╣реАред


GitHub рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди


All Articles