英文:
TableView changes not work correctly in swiftUI
问题
我在**SwiftUI**中使用了一个`UITableView`。我通过WebSocket获取数据,想要实时更新我的tableview。我成功地从网络中获取了数据,并在viewModel中进行了更新,然后将其传递给了`QuotesTableView`。
TableView没有正确地更新我的数据,即tableViewData的第一个值是Symbol1,但它在第一个`IndexPath`中显示的是Symbol1的数据。我在`cellForRowAt`函数中进行了调试,看到我获取到了正确的数据,但没有在单元格中显示正确的项目。它随机改变了表格中数据的顺序,但tableViewData的顺序并未改变。
英文:
I'm using a UITableView
in SwiftUI. I get data from WebSocket and I want to update my tableview live. I successfully get the data from the network and update it in viewModel and pass it to QuotesTableView
.
TableView does not update my data correctly, 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 does not show the correct item in cell. It randomly changes the order of the data in my tableview but tableviewData's order does 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
}
}
}
答案1
得分: 0
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])")
// 将托管控制器的根视图设置为此单元格的视图
let row = parent.tableViewData[indexPath.row]
let hostingController = UIHostingController(
rootView: AnyView(
QuotesCellView(item: row, selectAction: self.parent.selectClicked)
)
)
hostingController.view.backgroundColor = .clear
// 如果单元格正在重用,则移除任何先前的视图
tableViewCell.host?.view.removeFromSuperView()
// 创建并设置托管控制器
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
tableViewCell.setNeedsLayout()
return tableViewCell
}
英文:
To fix the problem with recycling cells you either need to change the data in the hosting view or create a new one. Your old code seems to want to make a new one (which is less efficient) so a small change to remove the old hosting view should fix that issue.
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
// remove any previous view if the cell is being reused
tableViewCell.host?.view.removeFromSuperView()
// create & setup hosting controller
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
tableViewCell.setNeedsLayout()
return tableViewCell
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论