I'm using uikit tableview in swiftui. I get data from WebSocket and I want to update my tableview live. I successfully get data from network and update it in viewModel and pass it to QuotesTableView. The problem is occur in here. TableView not update correctly my data, i.e tableViewData's first value is Symbol1 but it shows Symbol1 data in first indexpath. I debug it in cellForRowAt function and see I get correct data, but not show correct item in cell :( it randomly changes order of data in tableview. but tableviewData order not change.
@StateObject private var viewModel = QuotesViewModel()
var body: some View {
ZStack {
Color.init(hex: "#293c54").edgesIgnoringSafeArea(.all)
QuotesTableView(tableViewData: $viewModel.list)
.background(Color.clear)
}
}
}
import Combine
class QuotesViewModel: ObservableObject {
var cancellables = Set<AnyCancellable>()
// MARK: - Input
var selectedSymbols: String
/// Symbols list
@Published var list: [SymbolsInDataModel] = .init()
// MARK: - Output
// MARK: - Init
init() {
socket = SocketManager.shared
observeSocketValues()
bindView()
}
// MARK: - Business Logic
let socket: SocketManager
// MARK: - Config
}
// MARK: - Bind View
extension QuotesViewModel {
/// observe view actions in here...
func bindView() {
}
}
// MARK: - Observation Socket Data
extension QuotesViewModel {
func observeSocketValues() {
socket.$symbolsList.sink(receiveValue: { newSymbols in
self.list = newSymbols
})
.store(in: &cancellables)
socket.$symbolsList
.filter { !$0.isEmpty }
.first { _ in
self.list = self.socket.symbolsList
return true
}
.sink(receiveValue: {_ in})
.store(in: &cancellables)
}
}
struct QuotesTableView: UIViewRepresentable {
// The data source for the table view
@Binding var tableViewData: [SymbolsInDataModel]
var selectClicked: ((_ item: SymbolsInDataModel) -> Void)?
func makeUIView(context: Context) -> UITableView {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.backgroundColor = .clear
tableView.dataSource = context.coordinator
tableView.showsVerticalScrollIndicator = false
tableView.delegate = context.coordinator
tableView.register(HostingCell.self, forCellReuseIdentifier: "Cell")
return tableView
}
func updateUIView(_ uiView: UITableView, context: Context) {
// Reload the table view data whenever the data changes
uiView.reloadData()
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
// MARK: - CompetitionsTableView -> Coordinator
extension QuotesTableView {
class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
var parent: QuotesTableView
init(_ tableView: QuotesTableView) {
parent = tableView
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
parent.tableViewData.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! HostingCell
tableViewCell.backgroundColor = .clear
print("indexpath: \(indexPath.row) item: \(parent.tableViewData[indexPath.row])")
// Set the root view of the hosting controller to the view for this cell
let row = parent.tableViewData[indexPath.row]
let hostingController = UIHostingController(
rootView: AnyView(
QuotesCellView(item: row, selectAction: self.parent.selectClicked)
)
)
hostingController.view.backgroundColor = .clear
// create & setup hosting controller only once
if tableViewCell.host == nil {
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 = hostingController
}
tableViewCell.setNeedsLayout()
return tableViewCell
}
}
}