How to correctly make 2-way binding with UITableViewRepresentable and SwiftUI - uitableview

I'm creating my own custom SwiftUI List because I need to use some underlying methods of tableview. I would like to know how to correctly bind a TextField inside a UITableView Cell to my SwiftUI ViewModel.
So basically I have a textfield inside a SwiftUI View and use this as my tableview cell with the new UIHostingConfiguration struct. To check to see if things are working correctly I have a normal SwiftUI List above the TableViewRepresentable. If you type in a textfield of List, the TableViewRepresentable gets updated correctly as you type. But if you type in the textfield of TableViewRepresentable, only the Text beside the textfield updates. The List doesn't update the textfield's text. But then when you click inside any textfield inside the List, the actual update to the textfield happens.
I have found a way to get things to work but it doesn't seem correct. Let me know what you guys think. Hopefully someone has the answer. I have looked everywhere on the internet but nobody has done an example with UITableViewRepresentable or UICollectionViewRepresentable that works with 2 way binding from the cell to the ViewModel.
Also if anyone was curious why I need to use a TableView, it's to adjust the scrolling behavior of a List or TabView(.page). I need to get the velocity, like targetContentOffset method of scrollview, to have regular paging and then on fast swipe have fast paging.
Here is the ViewModel and data.
class Item: Identifiable, ObservableObject {
let id = UUID().uuidString
#Published var title: String
init(title: String) {
self.title = title
}
}
class ItemsViewModel: ObservableObject {
#Published var items: [Item] = Array(0...40).map({ Item(title: "\($0)")})
}
Hosting Cell. Just using as a sample cell to dequeue. This was the old way I was using to inject a SwiftUI View in a cell.
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()
}
}
Non-Working Code. Updated code to mimic sample code that #smileyborg suggested from Apple's sample code.
struct TableViewContent2: View {
#EnvironmentObject var itemsViewModel: ItemsViewModel
var body: some View {
VStack {
List($itemsViewModel.items) { $item in
TextField("", text: $item.title)
}
TableViewRepresentable2(itemsViewModel) { item in
CellView2(cellItem: item)
}
.ignoresSafeArea()
}
}
}
struct TableViewRepresentable2<Content: View>: UIViewRepresentable {
#ObservedObject private var itemsViewModel: ItemsViewModel
private var content: (Item) -> Content
init(_ itemsViewModel: ItemsViewModel, #ViewBuilder content: #escaping (Item) -> Content) {
self.itemsViewModel = itemsViewModel
self.content = content
}
private let cellID = "CellID"
func makeUIView(context: Context) -> UITableView {
let tableView = UITableView()
tableView.allowsSelection = false
tableView.delegate = context.coordinator
tableView.dataSource = context.coordinator
tableView.register(HostingCell<Content>.self, forCellReuseIdentifier: cellID)
return tableView
}
func updateUIView(_ uiView: UITableView, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self, content: content)
}
class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
var parent: TableViewRepresentable2
var content: (Item) -> Content
init(_ parent: TableViewRepresentable2, content: #escaping (Item) -> Content) {
self.parent = parent
self.content = content
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
parent.itemsViewModel.items.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
tableView.bounds.height / 4
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: parent.cellID, for: indexPath) as! HostingCell<Content>
let cellItem = parent.itemsViewModel.items[indexPath.row]
cell.configurationUpdateHandler = { cell, state in
cell.contentConfiguration = UIHostingConfiguration {
self.content(cellItem)
}
.margins(.all, 0)
}
return cell
}
}
}
struct CellView2: View {
#ObservedObject var cellItem: Item
var body: some View {
ZStack {
Color.blue
.ignoresSafeArea()
VStack {
TextField("Placeholder", text: $cellItem.title)
.background(RoundedRectangle(cornerRadius: 5).foregroundColor(.white))
.padding()
Text("item: \(cellItem.title)")
}
}
}
}
WorkAround code. Big Difference is the CellView. I found a way by passing in the cell item, finding the index to the item, and then creating two computed properties to give me the binding item and the item.
struct Item: Identifiable {
let id = UUID().uuidString
var title: String
}
struct TableViewContent: View {
#EnvironmentObject private var itemsViewModel: ItemsViewModel
var body: some View {
VStack {
List($itemsViewModel.items) { $item in
TextField("", text: $item.title)
}
TableViewRepresentable(itemsViewModel.items) { item in
CellView(cellItem: item)
}
}
}
}
struct TableViewRepresentable<Content: View>: UIViewRepresentable {
private var items: [Item]
private var content: (Item) -> Content
init(_ items: [Item], #ViewBuilder content: #escaping (Item) -> Content) {
self.items = items
self.content = content
}
private let cellID = "CellID"
func makeUIView(context: Context) -> UITableView {
let tableView = UITableView()
tableView.delegate = context.coordinator
tableView.dataSource = context.coordinator
tableView.register(HostingCell<Content>.self, forCellReuseIdentifier: cellID)
return tableView
}
func updateUIView(_ uiView: UITableView, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self, content: content)
}
class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
var parent: TableViewRepresentable
var content: (Item) -> Content
init(_ parent: TableViewRepresentable, content: #escaping (Item) -> Content) {
self.parent = parent
self.content = content
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
parent.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: parent.cellID, for: indexPath) as? HostingCell<Content> ?? UITableViewCell()
let item = parent.items[indexPath.row]
cell.contentConfiguration = UIHostingConfiguration {
content(item)
}
return cell
}
}
}
struct CellView: View {
#EnvironmentObject private var itemsViewModel: ItemsViewModel
var cellItem: Item
private var item$: Binding<Item> {
let index = itemsViewModel.items.firstIndex(where: { $0.id == cellItem.id })
return $itemsViewModel.items[index!]
}
private var item: Item {
let index = itemsViewModel.items.firstIndex(where: { $0.id == cellItem.id })
return itemsViewModel.items[index!]
}
var body: some View {
ZStack {
Color.blue
VStack {
TextField("Placeholder", text: item$.title)
.background(RoundedRectangle(cornerRadius: 5).foregroundColor(.white))
Text("item: \(item.title)")
}
.padding()
}
}
}

Related

SwiftUI Incorrect button behaviour in UITableView in Sheet view

I have problem with use Button as content in UITableView(UIViewRepresentable), which contains in Sheet. Button stayed pressed, when you try scroll table.
Here toy can download my proj https://github.com/MaksimBezdrobnoi/UITableViewInSheet
I create a simple UITableView where put SUI Button, and put Table in SUI Sheet.
Here my SUI view
struct TableSample: View {
var body: some View {
ZStack {
Color.clear
.sheet(isPresented: .constant(true)) {
AwesomeNewTable {
Button(action: {
print("HELLO")
}, label: {
Color.red
.frame(height: 30)
.padding(.horizontal, 16)
.padding(.bottom, 4)
})
}
}
}
}
}
And here my TableView
struct AwesomeNewTable<Content: View>: UIViewRepresentable {
private let content: () -> Content
init(content: #escaping () -> Content) {
self.content = content
}
func makeUIView(context: Context) -> UITableView {
let tableView = UITableView(frame: .zero, style: .grouped)
tableView.separatorStyle = .none
tableView.delegate = context.coordinator
tableView.dataSource = context.coordinator
tableView.register(HostingCell<Content>.self, forCellReuseIdentifier: "Cell")
return tableView
}
func updateUIView(_ uiView: UITableView, context: Context) {
context.coordinator.parent = self
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, UITableViewDelegate, UITableViewDataSource {
var parent: AwesomeNewTable
init(parent: AwesomeNewTable) {
self.parent = parent
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
150
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? HostingCell<Content> else {
return UITableViewCell()
}
let view = parent.content()
tableViewCell.setup(with: view)
return tableViewCell
}
}
}

`UIViewControllerRepresentable` table view disappears after long tap

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

Some cells of UITableView with UIViewRepresantable are positioned wrong

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:
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
}
}
}
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.
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?

How to Add Swipe Actions to List ? - SwiftUI

I want to add swipe actions to my list for show some actions.
All I want is add some actions for swipe left gesture on list row like mail application of ios.
I found this #michał-ziobro Swipe Actions Modifier solution but it is not working :/
Some parts of that code gave me errors and I fixed them with some extensions, I thought maybe that is the reason why it is not working.
struct TableViewConfigurator: UIViewControllerRepresentable {
var configure: (UITableView) -> Void = { _ in }
func makeUIViewController(context: UIViewControllerRepresentableContext<TableViewConfigurator>) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<TableViewConfigurator>) {
let tableViews = UIApplication.nonModalTopViewController()?.navigationController?.topViewController?.view.allSubViewsOf(type: UITableView.self) ?? [UITableView]()
for tableView in tableViews {
self.configure(tableView)
}
}
}
struct ListSwipeActions: ViewModifier {
#ObservedObject var coordinator = Coordinator()
func body(content: Content) -> some View {
return content
.background(TableViewConfigurator(configure: { tableView in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
tableView.delegate = self.coordinator
}
}))
}
class Coordinator: NSObject, ObservableObject, UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("Scrolling ....!!!")
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let isArchived = false
let title = isArchived ? "Unarchive" : "Archive"
let archiveAction = UIContextualAction(style: .normal, title: title, handler: {
(action, view, completionHandler) in
// update data source
completionHandler(true)
})
archiveAction.title = title
archiveAction.image = UIImage(systemName: "archivebox")!
archiveAction.backgroundColor = .systemYellow
let configuration = UISwipeActionsConfiguration(actions: [archiveAction])
return configuration
}
}
}
extension List {
func swipeActions() -> some View {
return self.modifier(ListSwipeActions())
}
}
and here is my list view :
struct NotificationsTab: View {
#ObservedObject var viewModel = NotificationsVM()
func delete(at offsets: IndexSet) {
viewModel.notifications.remove(atOffsets: offsets)
}
var body: some View {
NavigationView {
Group{
if viewModel.notifications.count > 0 {
List{
ForEach(self.viewModel.notifications, id: \.id){ notification in
HStack(spacing: 15){
WebImage(url: URL(string: notification.sender!.avatar!.userAvatarPathSmall()))
.resizable()
.indicator(.activity)
.aspectRatio(contentMode: .fill)
.animation(.easeInOut(duration: 0.5))
.transition(.fade)
.frame(width: 48, height: 48)
.cornerRadius(24)
Text(notification.sender!.name!).fontWeight(.bold) + Text(notification.message!)
}.padding(.vertical)
}.onDelete(perform: delete)
}.swipeActions()
}else{
LoadingView()
}
}
.navigationBarTitle("Notifications")
}.onAppear(){
self.viewModel.getNotifications()
}
}
}
struct NotificationsTab_Previews: PreviewProvider {
static var previews: some View {
NotificationsTab()
}
}

Drag item from one List to another List in SwiftUI

I have two Lists each with simple items. I can rearrange items within a list, with the code shown below. I want to be able to drag an item from one list and drop it into the other list. Not sure what I need to enable to make that happen. As is, I can drag something from one list all over the screen, but I can't drop it anywhere except within its own list; if I release the drag anywhere else, it just flies back to its original location.
Clearly, I need to add something(s) to enable the desired behavior; any ideas on what's required (not necessarily using .onMove -- that's within the same list) would be most appreciated.
struct ContentView: View {
#State private var users1 = ["AAA", "BBB", "CCC"]
#State private var users2 = ["DDD", "EEE", "FFF"]
#State private var isEditable = true
var body: some View {
HStack {
Spacer()
List {
ForEach(users1, id: \.self) { user in
Text(user)
.background(Color(.yellow))
}
.onMove(perform: move1)
}
.environment(\.editMode, isEditable ? .constant(.active) : .constant(.inactive))
Spacer()
List {
ForEach(users2, id: \.self) { user in
Text(user)
.background(Color(.orange))
}
.onMove(perform: move2)
}
.environment(\.editMode, isEditable ? .constant(.active) : .constant(.inactive))
Spacer()
}
}
func move1(from source: IndexSet, to destination: Int) {
users1.move(fromOffsets: source, toOffset: destination)
}
func move2(from source: IndexSet, to destination: Int) {
users2.move(fromOffsets: source, toOffset: destination)
}
}
I don't think this is possible with SwiftUI yet. This piece of code shows how you use two UITableViews. I hope you can improve this to achieve what you are looking for.
First create this class which does all the magic. You need to import UIKit and MobileCoreServices.
class TableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITableViewDragDelegate, UITableViewDropDelegate {
var leftTableView = UITableView()
var rightTableView = UITableView()
var removeIndex = IndexPath()
var leftItems: [String] = [
"Hello",
"What",
"is",
"happening"
]
var rightItems: [String] = [
"I",
"don't",
"know"
]
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
let string = tableView == leftTableView ? leftItems[indexPath.row] : rightItems[indexPath.row]
self.removeIndex = indexPath
guard let data = string.data(using: .utf8) else { return [] }
let itemProvider = NSItemProvider(item: data as NSData, typeIdentifier: kUTTypePlainText as String)
return [UIDragItem(itemProvider: itemProvider)]
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
let destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath {
destinationIndexPath = indexPath
} else {
let section = tableView.numberOfSections - 1
let row = tableView.numberOfRows(inSection: section)
destinationIndexPath = IndexPath(row: row, section: section)
}
coordinator.session.loadObjects(ofClass: NSString.self) { items in
guard let strings = items as? [String] else {
return
}
var indexPaths = [IndexPath]()
for (index, string) in strings.enumerated() {
let indexPath = IndexPath(row: destinationIndexPath.row + index, section: destinationIndexPath.section)
if tableView == self.leftTableView {
self.leftItems.insert(string, at: indexPath.row)
} else {
self.rightItems.insert(string, at: indexPath.row)
}
indexPaths.append(indexPath)
}
if tableView == self.leftTableView {
self.rightItems.remove(at: self.removeIndex.row)
self.rightTableView.deleteRows(at: [self.removeIndex], with: .automatic)
} else {
self.leftItems.remove(at: self.removeIndex.row)
self.leftTableView.deleteRows(at: [self.removeIndex], with: .automatic)
}
tableView.insertRows(at: indexPaths, with: .automatic)
}
}
override func viewDidLoad() {
super.viewDidLoad()
leftTableView.dataSource = self
rightTableView.dataSource = self
leftTableView.frame = CGRect(x: 0, y: 40, width: 150, height: 400)
rightTableView.frame = CGRect(x: 150, y: 40, width: 150, height: 400)
leftTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
rightTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
view.addSubview(leftTableView)
view.addSubview(rightTableView)
leftTableView.dragDelegate = self
leftTableView.dropDelegate = self
rightTableView.dragDelegate = self
rightTableView.dropDelegate = self
leftTableView.dragInteractionEnabled = true
rightTableView.dragInteractionEnabled = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == leftTableView {
return leftItems.count
} else {
return rightItems.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if tableView == leftTableView {
cell.textLabel?.text = leftItems[indexPath.row]
} else {
cell.textLabel?.text = rightItems[indexPath.row]
}
return cell
}
}
After that you wrap this:
struct TableView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> TableViewController {
let v = TableViewController()
return v
}
func updateUIViewController(_ viewController: TableViewController, context: Context) {
}
}
and finally use it:
struct ContentView: View {
var body: some View {
VStack {
TableView()
}
}
}
I hope this helps.

Resources