Does the List in Swiftui Reuse Cells Similar to Uitableview

Does the List in SwiftUI reuse cells similar to UITableView?

Yes, List is reusing its ListCoreCellHosts exactly like the way UITableView reuses its UITableViewCells.

Reference:

Investigating memory usage with Xcode shows that, when the number of the items is more than List could present at once, it just shows as much as it can and reuses them when they become occluded from the top or bottom of the list.

Sample Image

By tracing a single cell memory address, you can see it is reused over and over.
Another exciting tidbit is that ListCoreCellHost uses a hosting view that may refer to UIKit internally. (Not known well because it lacks documentation)

Sample Image

Reusability support in List in SwiftUI

List actually provides same technique as UITableView for reusable identifier. Your code make it like a scroll view.
The proper way to do is providing items as iterating data.

struct Item: Identifiable {
var id = UUID().uuidString
var name: String
}
@State private var items = (1...1000).map { Item(name: "Item \($0)") }
...
List(items) {
Text($0.name)
}

View hierarchy debugger shows only 17 rows in memory
Sample Image

How to show list of views from a dataSource like UITableView in SwiftUI

Dynamic views usually generated from dynamic data. So you should consider using a data structure for your repeating views, then build the list based on data like this:

struct Student: Identifiable {
let name: String
let id: Int
}

struct ContentView : View {

// Could be `@State Var` instead
let students = [
Student(name: "AAAAA", id: 1),
Student(name: "BBBBB", id: 2),
Student(name: "CCCCC", id: 3), // Notice that trailing comma is not problem here?
]

var body: some View {
List(students) { student in
Text(student.name)
}
}
}

Array should contain Identifiable objects (Recommended)

or if you not prefer to conform to Identifiable protocol you can use it like this:

struct Book {
let anyPropertyName: String
let title: String
}

struct ContentView : View {

// Could be `@State Var` instead
let books = [
Book(anyPropertyName: "AAAA", title: "1111"),
Book(anyPropertyName: "BBBB", title: "2222"),
Book(anyPropertyName: "CCCC", title: "3333")
]

var body: some View {
List(books.identified(by: \.anyPropertyName)) { book in
Text(book.title)
}
}
}

Note that dataSource can be @State var and it gives the ability to update the UI whenever any @State var changes.

Lastly, although it seems like it's not reusing, but actually it is! The limit of 10 static items has nothing to do with reusing.

Some cells of UITableView with UIViewRepresantable are positioned wrong

From Apple:

Thank you for your feedback, it is noted. Engineering has determined that there are currently no plans to address this issue.

We recommend using List, and filing a separate enhancement for any needs beyond what List can offer.

Reusing a UITableView with different view model types

Here is possible solution. Tested with Xcode 11.4 / iOS 13.4

If you move/duplicate all UITablView delegate/datasource callbacks into view model, then actually you don't need context coordinator at all, so generic entities can be as

// generic table view model protocol
protocol CustomTableViewModel: UITableViewDataSource, UITableViewDelegate {
func configure(tableView: UITableView)
}

// generic table view that depends only on generic view model
struct CustomTableView<ViewModel:ObservableObject & CustomTableViewModel>: UIViewRepresentable {
@ObservedObject var viewModel: ViewModel

func makeUIView(context: Context) -> UITableView {
let tableView = UITableView()
viewModel.configure(tableView: tableView)
return tableView
}

func updateUIView(_ tableView: UITableView, context: Context) {
tableView.reloadData()
}
}

And here is example of usage

// some specific model
class MyViewModel: NSObject, ObservableObject, CustomTableViewModel {
let items = ["one", "two", "three"]
let cellIdentifier = "MyCell"

func configure(tableView: UITableView) {
tableView.delegate = self
tableView.dataSource = self
tableView.register(MyTableViewCell.self, forCellReuseIdentifier: cellIdentifier)
tableView.separatorStyle = UITableViewCell.SeparatorStyle.none
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { items.count }

func numberOfRows(in section: Int) -> Int { 1 }

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! MyTableViewCell
cell.textLabel?.text = items[indexPath.row]
return cell
}
}

struct MyView: View {
@EnvironmentObject var myViewModel: MyViewModel

var body: some View {
CustomTableView(viewModel: myViewModel)
}
}

Note: actually with next decomposition step it could be separation of Presenter concept from ViewModel, but for simplicity of demo for direction above should be enough.



Related Topics



Leave a reply



Submit