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

基于Firebase构建类Strava社交网络:iOS端用户查询与关注功能疑问

嘿,我来帮你搞定这个Firebase用户搜索+关注状态区分的问题!其实核心就是先搜用户,再匹配当前用户的关注列表,我给你拆解成具体步骤,结合iOS代码示例来说明:

先明确数据库结构(假设你用Firestore)

首先对齐下常见的Firebase用户/关注数据结构,方便后续代码对应:

users 集合
├─ userId1
│  ├─ username: "johndoe"
│  ├─ displayName: "John Doe"
│  └─ ...其他用户字段
└─ userId2
   ├─ username: "janedoe"
   ├─ displayName: "Jane Doe"
   └─ ...其他用户字段

following 集合
├─ currentUserId(当前用户的ID)
│  ├─ followedUserId1: true  // 已关注用户的ID作为键,值为true
│  ├─ followedUserId2: true
│  └─ ...
└─ ...其他用户的关注数据

如果你的结构是把following数组存在用户文档里也没关系,后面代码会标注对应写法。

第一步:实现用户名搜索功能

先做基础的前缀匹配搜索(比全包含搜索高效,适合Strava这类社交场景):

// 假设你有一个Codable的User模型,包含id、username等字段
func searchUsers(withUsername keyword: String, completion: @escaping ([User]) -> Void) {
    guard let currentUid = Auth.auth().currentUser?.uid else {
        completion([])
        return
    }
    let db = Firestore.firestore()
    
    // 前缀匹配:搜索用户名以keyword开头的用户
    db.collection("users")
        .whereField("username", isGreaterThanOrEqualTo: keyword)
        .whereField("username", isLessThanOrEqualTo: keyword + "\u{f8ff}")
        .getDocuments { snapshot, error in
            guard let docs = snapshot?.documents, error == nil else {
                completion([])
                return
            }
            // 解析用户数据并排除当前用户自己
            let users = docs.compactMap { try? $0.data(as: User.self) }
                .filter { $0.id != currentUid }
            completion(users)
        }
}
第二步:区分已关注/未关注用户

这里分两种方案,根据你的用户量级选择:

方案一:一次性拉取当前用户的关注列表(适合关注人数不多的情况)

先拿到当前用户所有已关注的ID,再和搜索结果匹配:

func getCurrentUserFollowingIds(completion: @escaping ([String]) -> Void) {
    guard let currentUid = Auth.auth().currentUser?.uid else {
        completion([])
        return
    }
    let db = Firestore.firestore()
    
    // 对应上面的following集合结构
    db.collection("following").document(currentUid).getDocument { doc, error in
        guard let data = doc?.data(), error == nil else {
            completion([])
            return
        }
        // 提取所有已关注用户的ID
        let followingIds = data.keys.compactMap { $0 }
        completion(followingIds)
    }
    
    // 如果你的结构是用户文档里有following数组,替换成下面这段:
    // db.collection("users").document(currentUid).getDocument { doc, error in
    //     let currentUser = try? doc?.data(as: User.self)
    //     completion(currentUser?.following ?? [])
    // }
}

// 结合搜索和状态匹配
searchUsers(withUsername: "jane") { searchResults in
    getCurrentUserFollowingIds { followingIds in
        // 给每个搜索结果标记关注状态
        let usersWithStatus = searchResults.map { user in
            var mutableUser = user
            mutableUser.isFollowed = followingIds.contains(user.id)
            return mutableUser
        }
        // 回到主线程更新UI
        DispatchQueue.main.async {
            self.userList = usersWithStatus
            self.tableView.reloadData()
        }
    }
}

方案二:逐个查询关注状态(适合关注人数极多的情况)

如果当前用户关注了上千人,一次性拉取所有ID太占带宽,可以对每个搜索结果单独查询:

func checkIfUserIsFollowed(userId: String, completion: @escaping (Bool) -> Void) {
    guard let currentUid = Auth.auth().currentUser?.uid else {
        completion(false)
        return
    }
    let db = Firestore.firestore()
    
    // 检查当前用户的关注文档里是否存在该用户ID
    db.collection("following").document(currentUid).getDocument { doc, error in
        guard let data = doc?.data(), error == nil else {
            completion(false)
            return
        }
        completion(data[userId] != nil)
    }
}

// 使用方式
searchUsers(withUsername: "jane") { searchResults in
    let dispatchGroup = DispatchGroup()
    var usersWithStatus: [User] = []
    
    for user in searchResults {
        dispatchGroup.enter()
        checkIfUserIsFollowed(userId: user.id) { isFollowed in
            var mutableUser = user
            mutableUser.isFollowed = isFollowed
            usersWithStatus.append(mutableUser)
            dispatchGroup.leave()
        }
    }
    
    // 所有查询完成后更新UI
    dispatchGroup.notify(queue: .main) {
        self.userList = usersWithStatus
        self.tableView.reloadData()
    }
}
第三步:列表视图处理关注/取消关注按钮

在cell里根据isFollowed状态设置按钮,并实现点击切换逻辑:

// 自定义UserCell示例
class UserCell: UITableViewCell {
    @IBOutlet weak var usernameLabel: UILabel!
    @IBOutlet weak var followButton: UIButton!
}

// 列表数据源方法
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! UserCell
    let user = userList[indexPath.row]
    
    cell.usernameLabel.text = user.username
    cell.followButton.setTitle(user.isFollowed ? "已关注" : "关注", for: .normal)
    cell.followButton.tag = indexPath.row
    cell.followButton.addTarget(self, action: #selector(toggleFollowAction(_:)), for: .touchUpInside)
    
    return cell
}

// 切换关注状态的方法
@objc func toggleFollowAction(_ sender: UIButton) {
    let index = sender.tag
    let targetUser = userList[index]
    guard let currentUid = Auth.auth().currentUser?.uid else { return }
    
    let db = Firestore.firestore()
    let followingDocRef = db.collection("following").document(currentUid)
    
    if targetUser.isFollowed {
        // 取消关注:删除对应字段
        followingDocRef.updateData([targetUser.id: FieldValue.delete()]) { [weak self] error in
            guard error == nil else { return }
            DispatchQueue.main.async {
                self?.userList[index].isFollowed = false
                self?.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
            }
        }
    } else {
        // 关注:添加字段
        followingDocRef.setData([targetUser.id: true], merge: true) { [weak self] error in
            guard error == nil else { return }
            DispatchQueue.main.async {
                self?.userList[index].isFollowed = true
                self?.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
            }
        }
    }
}
额外优化建议
  • 缓存关注列表:把当前用户的关注ID存在本地(比如UserDefaults或者ViewModel),避免每次搜索都重新拉取,定期刷新即可。
  • 搜索性能优化:如果用户量很大,建议用Firebase Cloud Functions+Algolia做全文搜索,Firestore的前缀查询在数据量过万后性能会下降。
  • 加载状态处理:搜索时显示ActivityIndicator,给用户明确的反馈。

内容的提问来源于stack exchange,提问作者swifferina

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:50:31