Skip to content

Instantly share code, notes, and snippets.

@achernoprudov
Last active August 2, 2020 09:59
Show Gist options
  • Save achernoprudov/0cb09d10922df09a82ee6f808433c8c9 to your computer and use it in GitHub Desktop.
Save achernoprudov/0cb09d10922df09a82ee6f808433c8c9 to your computer and use it in GitHub Desktop.
struct CustomTableSection<HeaderItem, Item> {
var header: HeaderItem
var items: [Item]
}
struct CustomTableView<Item, Content: View, HeaderItem, HeaderContent: View>: UIViewRepresentable {
@Binding
var sections: [CustomTableSection<HeaderItem, Item>]
var cellBuilder: (Item) -> Content
var headerBuilder: (HeaderItem) -> HeaderContent
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(
CustomTableViewHostingCell.self,
forCellReuseIdentifier: CustomTableViewHostingCell.id
)
collectionView.register(
CustomTableViewHostingHeaderCell.self,
forHeaderFooterViewReuseIdentifier: CustomTableViewHostingHeaderCell.id
)
return collectionView
}
func updateUIView(_ uiView: UITableView, context: Context) {
// hack to make UITableView update
DispatchQueue.main.async {
context.coordinator.sections = self.sections
uiView.reloadData()
}
}
func makeCoordinator() -> CustomTableViewCoordinator<Item, Content, HeaderItem, HeaderContent> {
CustomTableViewCoordinator(
sections: sections,
cellBuilder: cellBuilder,
headerBuilder: headerBuilder
)
}
}
class CustomTableViewCoordinator<Item, CellContent: View, HeaderItem, HeaderContent: View>: NSObject, UITableViewDataSource, UITableViewDelegate {
var sections: [CustomTableSection<HeaderItem, Item>]
let cellBuilder: (Item) -> CellContent
let headerBuilder: (HeaderItem) -> HeaderContent
init(
sections: [CustomTableSection<HeaderItem, Item>],
cellBuilder: @escaping (Item) -> CellContent,
headerBuilder: @escaping (HeaderItem) -> HeaderContent
) {
self.sections = sections
self.cellBuilder = cellBuilder
self.headerBuilder = headerBuilder
}
// MARK: - UITableViewDataSource
func numberOfSections(in tableView: UITableView) -> Int {
sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
sections[section].items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: CustomTableViewHostingCell.id,
for: indexPath
) as! CustomTableViewHostingCell
let section = sections[indexPath.section]
let data = section.items[indexPath.row]
let view = cellBuilder(data)
// create & setup hosting controller only once
if cell.host == nil {
let controller = UIHostingController(rootView: AnyView(view))
cell.setupHost(with: controller)
} else {
// reused cell, so just set other SwiftUI root view
cell.host?.rootView = AnyView(view)
}
cell.setNeedsLayout()
return cell
}
func tableView(
_ tableView: UITableView,
viewForHeaderInSection section: Int
) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(
withIdentifier: CustomTableViewHostingHeaderCell.id
) as! CustomTableViewHostingHeaderCell
let section = sections[section]
let view = headerBuilder(section.header)
if headerView.host == nil {
let controller = UIHostingController(rootView: AnyView(view))
headerView.setupHost(with: controller)
} else {
// reused cell, so just set other SwiftUI root view
headerView.host?.rootView = AnyView(view)
}
headerView.setNeedsLayout()
return headerView
}
}
/// Cell for holding hosting controller
private class CustomTableViewHostingCell: UITableViewCell {
static let id: String = "CustomTableViewHostingCell"
var host: UIHostingController<AnyView>?
func setupHost(with controller: UIHostingController<AnyView>) {
host = controller
let cellContent = controller.view!
cellContent.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(cellContent)
NSLayoutConstraint.activate([
cellContent.topAnchor.constraint(equalTo: contentView.topAnchor),
cellContent.leftAnchor.constraint(equalTo: contentView.leftAnchor),
cellContent.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
cellContent.rightAnchor.constraint(equalTo: contentView.rightAnchor),
])
}
}
/// Cell for holding hosting controller
private class CustomTableViewHostingHeaderCell: UITableViewHeaderFooterView {
static let id: String = "CustomTableViewHostingHeaderCell"
var host: UIHostingController<AnyView>?
func setupHost(with controller: UIHostingController<AnyView>) {
host = controller
let cellContent = controller.view!
cellContent.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(cellContent)
NSLayoutConstraint.activate([
cellContent.topAnchor.constraint(equalTo: contentView.topAnchor),
cellContent.leftAnchor.constraint(equalTo: contentView.leftAnchor),
cellContent.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
cellContent.rightAnchor.constraint(equalTo: contentView.rightAnchor),
])
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment