SwiftUI中无法获取搜索列表选中项的selectedDataID问题
问题:搜索消息列表点击单元格时selectedDataID返回nil
我有两个API,分别用于展示全部消息列表和搜索关键词匹配的消息列表。在全部列表中点击单元格能正常获取selectedDataID,但点击搜索列表中的单元格时,selectedDataID返回nil,不过仍能跳转到SentAndInboxChatView。需要解决搜索列表中点击单元格也能获取到selectedDataID的问题。
代码说明
当前代码可正常展示全部列表和搜索列表,但搜索时点击单元格,selectedDataID输出为nil,核心代码如下:
MessageListView 视图代码
struct MessageListView: View { @StateObject private var viewModel = AllMessagesListViewModel() @State private var selectedDataID: String? @State var id: String = "" @State private var gotoChat = false @State private var searchText = "" @State private var showingSearchField = false var body: some View { ZStack { Color.hexF4F4FC .ignoresSafeArea() VStack(spacing: 0) { searchFieldView List { if viewModel.allMesgs.isEmpty { Text("No records found") } else { ForEach(0..<viewModel.allMesgs.count, id: \.self) { ind in Button { selectedDataID = viewModel.allMesgs[ind].id gotoChat = true } label: { messageView(user: viewModel.allMesgs[ind]) } if ind == viewModel.allMesgs.count - 1 { Spacer(minLength: 30) } } .padding(.top, 10) } } .navigationDestination(isPresented: $gotoChat) { SentAndInboxChatView(idInbox: selectedDataID ?? "", messageType: MessageType.Inbox.rawValue) .toolbar(.hidden, for: .navigationBar) } .listStyle(.plain) } .onAppear { viewModel.fetchAllMessages(id: id) { status in } } } } private var searchFieldView: some View { ZStack { Rectangle() .fill(Color.white) .cornerRadius(10) HStack { Image(systemName: "magnifyingglass").foregroundColor(.gray) .padding(.leading, 6) TextField("Search", text: $searchText) .onChange(of: searchText) { newValue in if newValue.count >= 3 { viewModel.fetchAllMessagesSearch(id: id, Query: newValue, showHud: false) { status in } print("selectedDataID.... \(selectedDataID) ") } else { viewModel.fetchAllMessages(id: id, showHud: false) { status in } } } Spacer() Button(action: { searchText = "" viewModel.fetchAllMessages(id: id) { status in } showingSearchField = false }) { Image(systemName: "xmark.circle.fill") .foregroundColor(.gray) .padding(8) } } .padding(EdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 8)) } } }
AllMessagesListViewModel 代码
@MainActor class AllMessagesListViewModel: ObservableObject { @Published var allMesgs: [AllMessage] = [] @Published var senderDtl: SenderDTL? func fetchAllMessages(id: String, showHud: Bool = true, _ completion: @escaping StatusCompletion) { if APIManager.shared.isConnectedToInternet { let url = APIEndPoint.messageFrom let params: [String: Any] = ["ID": id] APIManager.shared.callDecodableApiRequest(with: url, method: .get, params: params, showHud: showHud) { (statusCode, error, model: AllMessagesModel?) in if statusCode.isSuccess { self.senderDtl = model?.senderDTL self.allMesgs = model?.allMessages ?? [] completion(true) } else { showErrorToast(message: K.Msgs.error) completion(false) } } } else { showInfoToast(message: K.Msgs.noInternet) completion(false) } } func fetchAllMessagesSearch(id: String, Query: String, showHud: Bool = true, _ completion: @escaping StatusCompletion) { if APIManager.shared.isConnectedToInternet { let url = APIEndPoint.messageFromSearch let params: [String: Any] = ["ID": id, "Query": Query] APIManager.shared.callDecodableApiRequest(with: url, method: .get, params: params, showHud: showHud) { (statusCode, error, model: AllMessagesModel?) in if statusCode.isSuccess { self.senderDtl = model?.senderDTL self.allMesgs = model?.allMessages ?? [] completion(true) } else { showErrorToast(message: K.Msgs.error) completion(false) } } } else { showInfoToast(message: K.Msgs.noInternet) completion(false) } } }
输出日志:
selectedDataID.... nil
问题分析
- 索引遍历的隐患:当前使用
ForEach(0..<viewModel.allMesgs.count, id: \.self)基于索引遍历数组,当搜索API返回新数据替换allMesgs后,索引对应的元素可能出现绑定失效或错位,导致点击时无法正确获取对应元素的id。 - 日志打印时机错误:
onChange(of: searchText)中打印selectedDataID的时机是搜索文本变化时,此时用户还未点击单元格,打印的是旧值或nil,并非点击单元格时的真实值。 - 导航状态同步问题:使用
isPresented触发导航时,gotoChat的状态更新可能快于selectedDataID的赋值,导致跳转时selectedDataID尚未完成赋值。
解决方案
方案1:改用元素遍历的ForEach(推荐)
替换基于索引的遍历方式,直接遍历allMesgs数组元素,确保点击时能准确获取当前元素的id:
List { if viewModel.allMesgs.isEmpty { Text("No records found") } else { // 直接遍历数组元素,需确保AllMessage遵循Identifiable协议 ForEach(viewModel.allMesgs) { message in Button { selectedDataID = message.id gotoChat = true } label: { messageView(user: message) } } .padding(.top, 10) // 统一添加底部间距,无需判断最后一个元素 Spacer(minLength: 30) } }
如果AllMessage未遵循Identifiable协议,可在ForEach中指定id:
ForEach(viewModel.allMesgs, id: \.id) { message in
方案2:验证搜索API返回数据
确认搜索接口返回的AllMessage对象的id字段是否有效,可在ViewModel的搜索回调中添加日志:
func fetchAllMessagesSearch(id: String, Query: String, showHud: Bool = true, _ completion: @escaping StatusCompletion) { // ... 现有代码 if statusCode.isSuccess { self.senderDtl = model?.senderDTL self.allMesgs = model?.allMessages ?? [] // 打印检查每个消息的id self.allMesgs.forEach { msg in print("搜索结果消息ID:\(msg.id ?? "无ID")") } completion(true) } // ... }
若打印结果显示id为nil,需检查API接口返回的数据格式是否正确。
方案3:优化导航跳转逻辑
改用navigationDestination(item:)替代isPresented,直接绑定selectedDataID,避免状态同步问题:
// 移除gotoChat状态变量 // @State private var gotoChat = false // 修改导航代码 .navigationDestination(item: $selectedDataID) { selectedID in SentAndInboxChatView(idInbox: selectedID, messageType: MessageType.Inbox.rawValue) .toolbar(.hidden, for: .navigationBar) } // 按钮点击逻辑简化为直接赋值selectedDataID Button { selectedDataID = message.id // 赋值后自动触发导航 } label: { messageView(user: message) }
这种方式更符合SwiftUI的状态驱动设计,确保跳转时selectedDataID必有有效值。
内容的提问来源于stack exchange,提问作者Swift
相关产品推荐
相关产品推荐

