You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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


问题分析

  1. 索引遍历的隐患:当前使用ForEach(0..<viewModel.allMesgs.count, id: \.self)基于索引遍历数组,当搜索API返回新数据替换allMesgs后,索引对应的元素可能出现绑定失效或错位,导致点击时无法正确获取对应元素的id。
  2. 日志打印时机错误:onChange(of: searchText)中打印selectedDataID的时机是搜索文本变化时,此时用户还未点击单元格,打印的是旧值或nil,并非点击单元格时的真实值。
  3. 导航状态同步问题:使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 13:29:56