Does the List in SwiftUI reuse cells similar to UITableView?
Yes, List
is reusing its ListCoreCellHost
s exactly like the way UITableView
reuses its UITableViewCell
s.
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
.
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)
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
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
Extract Last Word in String with Swift
How to Properly Add Child View Controller in iOS 8 with Swift
Instantiateviewcontrollerwithidentifier - Storyboard Id Set But Still Not Working
How to Detect Whether an iOS Device Is in Silent Mode or Not
How to Search for Any Uiwebview Component Usage Inside a Current Project
Ios8 Extension and Container App How to Share Data
Uilabel, Uifont and Utf-8 Triangle
Programmatically Detect Dark Mode in Swiftui to Display Appropriate Image
Programmatically Sending an App to Background
How to Get an Error Description When Playback Fails on Mpmovieplayercontroller
Autolayout: Uiview Within Uiview Has Incorrect Frame
Ios: Diddiscoverperipheral Not Called in Background Mode
How to Integrate Payu Money in Swift
[Nsthread Ismainthread]' Always Returns Yes
Stream Aws S3 Hls Videos in iOS Browsers
Adding Views. Storyboard VS. Programmatically