Uitableview with Uiviewrepresentable in Swiftui

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.

demo

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



Leave a reply



Submit