UITableView with UIViewRepresentable in SwiftUI
I assume this is due to corrupted reused table view cells... (and probably lost hosting controllers, because there where created on stack and not stored anywhere)
Please find below corrected a bit your code with mentioned fixes. Tested & worked with Xcode 11.2 / iOS 13.2.
Here is code (with some comments inline):
class HostingCell: UITableViewCell { // just to hold hosting controller
var host: UIHostingController<AnyView>?
}
struct UIList: UIViewRepresentable {
var rows: [String]
func makeUIView(context: Context) -> UITableView {
let collectionView = UITableView(frame: .zero, style: .plain)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
collectionView.register(HostingCell.self, forCellReuseIdentifier: "Cell")
return collectionView
}
func updateUIView(_ uiView: UITableView, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(rows: rows)
}
class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
var rows: [String]
init(rows: [String]) {
self.rows = rows
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
self.rows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! HostingCell
let view = Text(rows[indexPath.row])
.frame(height: 50).background(Color.blue)
// create & setup hosting controller only once
if tableViewCell.host == nil {
let controller = UIHostingController(rootView: AnyView(view))
tableViewCell.host = controller
let tableCellViewContent = controller.view!
tableCellViewContent.translatesAutoresizingMaskIntoConstraints = false
tableViewCell.contentView.addSubview(tableCellViewContent)
tableCellViewContent.topAnchor.constraint(equalTo: tableViewCell.contentView.topAnchor).isActive = true
tableCellViewContent.leftAnchor.constraint(equalTo: tableViewCell.contentView.leftAnchor).isActive = true
tableCellViewContent.bottomAnchor.constraint(equalTo: tableViewCell.contentView.bottomAnchor).isActive = true
tableCellViewContent.rightAnchor.constraint(equalTo: tableViewCell.contentView.rightAnchor).isActive = true
} else {
// reused cell, so just set other SwiftUI root view
tableViewCell.host?.rootView = AnyView(view)
}
tableViewCell.setNeedsLayout()
return tableViewCell
}
}
}
Added demo code for just UIList
itself - works fine with pro models as well.
struct TestUIList: View {
var body: some View {
UIList(rows: generateRows())
}
func generateRows() -> [String] {
(0..<100).reduce([]) { $0 + ["Row \($1)"] }
}
}
SwiftUI - Navigate from UITableView (UIViewRepresentable) to View
Using this way or other way, you can get didSelectRowAt method
Tableview
struct TableView: UIViewRepresentable {
var completionHandler:((String) -> Void) = {_ in }
@State var rows = ["London", "Paris", "Oslo"]
func makeUIView(context: Context) -> UITableView {
let table = UITableView()
table.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
table.dataSource = context.coordinator
table.delegate = context.coordinator
return table
}
func updateUIView(_ uiView: UITableView, context: Context) {
}
func makeCoordinator() -> Coordinator {
//return Coordinator(rows: $rows)
return Coordinator(self)
}
class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
//@Binding var rows: [String]
@State var parent: TableView
init(_ parent: TableView){
self.parent = parent
}
/*
init(rows: Binding<[String]>) {
self._rows = rows
}*/
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.parent.rows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
cell?.textLabel?.text = self.parent.rows[indexPath.row]
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.parent.completionHandler(self.parent.rows[indexPath.row])
//let host = UIHostingController(rootView: destination)
//TODO: - Navigate to Destination
//navigationController.pushViewController(host, animated: true)
}
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let add = UIContextualAction(style: .normal,title: "Add") { (action, view, success) in
success(true)
}
add.backgroundColor = .gray
return UISwipeActionsConfiguration(actions: [add])
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let remove = UIContextualAction(style: .normal,title: "Remove") { (action, view, success) in
success(true)
}
remove.backgroundColor = .red
let edit = UIContextualAction(style: .normal,title: "Edit") { (action, view, success) in
success(true)
}
edit.backgroundColor = .gray
let color = UIContextualAction(style: .normal, title: "Color") { (action, view, success) in
success(true)
}
return UISwipeActionsConfiguration(actions: [remove, edit, color])
}
}
}
ContentView
struct ContentView: View {
@State var selectedString: String = ""
@State var isPresent: Bool = false
var body: some View {
NavigationView {
VStack {
HStack {
Text("Some other Views")
}
TableView(completionHandler: { (s) in
self.selectedString = s
self.isPresent = true
}, rows: ["A","B","C","D"])
NavigationLink(destination: Text(self.selectedString), isActive: $isPresent) {
EmptyView()
}
/*TableView(completionHandler: { str in
self.selectedString = str
self.isPresent = true
}, rows: ["A","B","C","D"])*/
}
.sheet(isPresented: self.$isPresent) {
Text(self.selectedString)
}
}
}
}
TableView Reload Not working with UIViewRepresentable in SwiftUI
I assume the UITableView
member just recreated, because it is member, instead use as below
Tested with Xcode 12 / iOS 14
struct CustomListView: View {
@StateObject var model = VM()
var body: some View {
print("******* BODY")
return UIListView(reload: $model.reload, items: $model.items)
.onAppear {
model.fetchData()
}
}
}
class VM: ObservableObject {
@Published var reload = false
var items = [String]()
func fetchData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.items = ["asdas", "asdasdas"]
self.reload.toggle()
}
}
}
struct UIListView: UIViewRepresentable {
@Binding var reload: Bool
@Binding var items: [String]
func makeUIView(context: Context) -> UITableView {
print("*************** make")
let tableView = UITableView()
tableView.dataSource = context.coordinator
return tableView
}
func updateUIView(_ tableView: UITableView, context: Context) {
print("*************** update", items.count)
print("********* RELOAD", reload)
if reload {
DispatchQueue.main.async {
tableView.reloadData()
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
extension UIListView {
final class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
private var parent: UIListView
init(_ parent: UIListView) {
self.parent = parent
}
//MARK: UITableViewDataSource Methods
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return parent.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("*********Cell for row at")
let cell = UITableViewCell()
cell.textLabel?.text = parent.items[indexPath.row]
return cell
}
}
}
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.
Related Topics
How to Create an Alert in a Swift File Model That Works for Various View Controller
How to Instantiate and Load a View Controller Before Segueing to It Using Swift
How to Update a Swiftui View State from Outside (Uiviewcontroller for Example)
Sirikit, How to Display Response for Start Workout Intent
How to Pass Values from a Pop Up View Controller to the Previous View Controller
Firebase Checking for Null Value (Swift)
How to Change Status Bar Text Color Immediately in Swift in iOS 13
Firebase: Provided Bucket Does Not Match the Storage Bucket of the Current Instance in Swift
How to Change Pin Color in Mapkit Under the Same Annotationview (Swift3)
How to Debug Swift Playgroundbook
How to Set Up Array for Multi Annotations with Swift
Ios11 Uibarbuttonitem Not Working
How to Pass a Swift Object to JavaScript (Wkwebview/Swift)
Mapbox Navigation Pod Unable to Install with Objectivec Where I am Using Xcode 9.2
Ambiguous Use of Registerclass with Swift