英文:
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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论