I have a UIViewControllerRepresentable
wrapper for UITableViewController
and am using swift composable architecture, which is probably irrelevant to the issue.
Here's my table view wrapper code, including the context menu code (I have omitted quite a lot of setup code):
public struct List<EachState, EachAction, RowContent, RowPreview, Destination, Data, ID>: UIViewControllerRepresentable, KeyPathUpdateable
where Data: Collection, RowContent: View, RowPreview: View, Destination: View, EachState: Identifiable, EachState.ID == ID {
private var actionProvider: (IndexSet) -> UIMenu? = { _ in nil }
private var previewProvider: (Store<EachState, EachAction>) -> RowPreview? = { _ in nil }
// setup code
public func makeUIViewController(context: Context) -> UITableViewController {
let tableViewController = UITableViewController()
tableViewController.tableView.translatesAutoresizingMaskIntoConstraints = false
tableViewController.tableView.dataSource = context.coordinator
tableViewController.tableView.delegate = context.coordinator
tableViewController.tableView.separatorStyle = .none
tableViewController.tableView.register(HostingCell<RowContent>.self, forCellReuseIdentifier: "Cell")
return tableViewController
}
public func updateUIViewController(_ controller: UITableViewController, context: Context) {
context.coordinator.rows = data.enumerated().map { offset, item in
store.scope(state: { $0[safe: offset] ?? item },
action: { (item.id, $0) })
}
controller.tableView.reloadData()
}
public func makeCoordinator() -> Coordinator {
Coordinator(rows: [],
content: content,
onDelete: onDelete,
actionProvider: actionProvider,
previewProvider: previewProvider,
destination: destination)
}
public func previewProvider(_ provider: @escaping (Store<EachState, EachAction>) -> RowPreview?) -> Self {
update(\.previewProvider, value: provider)
}
public func destination(_ provider: @escaping (Store<EachState, EachAction>) -> Destination?) -> Self {
update(\.destination, value: provider)
}
public class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
fileprivate var rows: [Store<EachState, EachAction>]
private var content: (Store<EachState, EachAction>) -> RowContent
private var actionProvider: (IndexSet) -> UIMenu?
private var previewProvider: (Store<EachState, EachAction>) -> RowPreview?
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
rows.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? HostingCell<RowContent>,
let view = rows[safe: indexPath.row] else {
return UITableViewCell()
}
tableViewCell.setup(with: content(view))
return tableViewCell
}
public func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
onDelete(IndexSet(integer: indexPath.item))
}
}
public func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
guard let store = rows[safe: indexPath.row] else { return nil }
return UIContextMenuConfiguration(
identifier: nil,
previewProvider: {
guard let preview = self.previewProvider(store) else { return nil }
let hosting = UIHostingController<RowPreview>(rootView: preview)
return hosting
},
actionProvider: { _ in
self.actionProvider(IndexSet(integer: indexPath.item))
})
}
}
}
private class HostingCell<Content: View>: UITableViewCell {
var host: UIHostingController<Content>?
func setup(with view: Content) {
if host == nil {
let controller = UIHostingController(rootView: view)
host = controller
guard let content = controller.view else { return }
content.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(content)
content.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
content.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
content.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
content.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
} else {
host?.rootView = view
}
setNeedsLayout()
}
}
And here's an example usage:
private struct ClassView: View {
let store = Store<ClassState, ClassAction>(
initialState: ClassState(),
reducer: classReducer,
environment: ClassEnv()
)
var body: some View {
WithViewStore(store) { viewStore in
CoreInterface.List(store.scope(state: \.people, action: ClassAction.personAction)) { store in
PersonView(store: store)
}
.actionProvider { indices in
let delete = UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { _ in
viewStore.send(.remove(indices))
}
return UIMenu(title: "", children: [delete])
}
.previewProvider { viewStore in
Text("preview")
}
}
}
}
The issue is as follows: when I long tap on a cell to show the context menu, then dismiss it and scroll up, the table view disappears. This only happens when it's inside a NavigationView
. Here is a short video of the issue.
The project is on github. The table view wrapper is in InternalFrameworks/Core/CoreInterface/Views/List
, usage is in InternalFrameworks/Screens/QuickWorkoutsList/Source/QuickWorkoutsList
. In order to run the project, you'll need xcodegen. Run
brew install xcodegen
xcodegen generate
from `UIViewControllerRepresentable ` table view disappears after long tap
No comments:
Post a Comment