1

I am trying to use UITableView with UIViewRepresantable, it kinda works, but not really. Some rows are placed in the wrong place, either overlapping other rows, either extra padding is added. Also it doesn't seem to adapt the row height to the content of the row even if I set automaticDimension.

Here is the demo:

enter image description here

Here is the code:

import SwiftUI

struct ContentView: View {
    @State var rows : [String] = []
    var listData = ListData()
    
    var body: some View {
        VStack {
            Button(action: {
                self.getDataFromTheServer()
            }) {
               Text("Get 100 entries from the server")
            }
            UIList(rows: $rows)
        }
    }
    
    func getDataFromTheServer() {
        for _ in 1...100 {
            self.rows.append(self.listData.data)
            self.listData.data.append("a")
        }
    }

}

class ListData {
    var data: String = ""
}

class HostingCell: UITableViewCell {
    var host: UIHostingController<AnyView>?
}

struct UIList: UIViewRepresentable {

    @Binding var rows: [String]

    func makeUIView(context: Context) -> UITableView {
        let tableView = UITableView(frame: .zero, style: .plain)
        tableView.dataSource = context.coordinator
        tableView.delegate = context.coordinator
        tableView.register(HostingCell.self, forCellReuseIdentifier: "Cell")
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = UITableView.automaticDimension
        return tableView
    }

    func updateUIView(_ uiView: UITableView, context: Context) {
        DispatchQueue.main.async {
            uiView.reloadData()
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(rows: $rows)
    }

    class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {

        @Binding var rows: [String]

        init(rows: Binding<[String]>) {
            self._rows = rows
        }

        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 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])
                    .background(Color.blue)
                    .lineLimit(nil)

            // create & setup hosting controller only once
            if tableViewCell.host == nil {
                let hostingController = UIHostingController(rootView: AnyView(view))
                tableViewCell.host = hostingController

                let tableCellViewContent = hostingController.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
        }
    }
}
Claudiu
  • 485
  • 1
  • 4
  • 17

2 Answers2

1

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.

Claudiu
  • 485
  • 1
  • 4
  • 17
0

UIHostingControllers don’t size themselves very well, which I suspect is what’s wrecking your autolayout. Try adding this after you set the controller’s rootView:

hostingController.preferredContentSize = hostingController.sizeThatFits( )

That said, you may want to avoid the UIHostingController entirely… if you’re going to the trouble of using UITableView, why not make the cell in UIKit too? Or if you need SwiftUI for the rows, why not use List instead of hosting a UITableView?

Adam
  • 4,405
  • 16
  • 23
  • this doesn't work. I am already using the List from SwiftUI but because it doesn't work very well with lots of data and a lot of refreshing I wanted to use UITableView. UITableView is working better for me but unfortunately it behaves like you saw. – Claudiu Jul 13 '20 at 06:26