You need to enable JavaScript to run this app. RxSwift+MVVM架构下,如何在SearchController中实现Hashtag搜索?
优惠活动
大模型
产品
解决方案
定价
更多

RxSwift+MVVM架构下,如何在SearchController中实现Hashtag搜索?

RxSwift + MVVM模式下Hashtag搜索落地方案

现有代码核心问题:ViewModel的Input层仅绑定了初始加载信号,完全未关联搜索栏的输入事件,搜索触发链路未打通,按以下步骤改造即可,原有业务逻辑无需改动。

1. ViewController层改造:打通搜索输入信号

把搜索栏输入、搜索按钮点击事件封装成Rx可订阅的信号,传给ViewModel层:

func setUI() {
    // 初始化搜索栏
    self.searchBar = UISearchBar(frame: CGRect(x: 300, y: 0, width: 300, height: 20))
    
    // 搜索输入信号:加防抖、去重,避免输入过程中频繁触发请求
    let searchInput = searchBar.rx.text.orEmpty
        .debounce(.milliseconds(300), scheduler: MainScheduler.instance)
        .distinctUntilChanged()
        .asSignal(onErrorJustReturn: "")
    
    // 搜索按钮点击触发:取最新的搜索文本作为关键词
    let searchTrigger = searchBtn.rx.tap
        .withLatestFrom(searchInput)
        .asSignal(onErrorJustReturn: "")
    
    // 原有动画逻辑完全保留
    searchBtn.rx.tap.subscribe(onNext: { _ in
        self.navigationItem.searchController = self.searchController
        self.navigationItem.hidesSearchBarWhenScrolling = false
        UIView.animate(withDuration: 0.25) {
            self.searchBar.frame = CGRect(x: 0, y: 0, width: 300, height: 20)
        }
    }).disposed(by: rx.disposeBag)
    
    // 绑定ViewModel
    let viewModel = SearchViewModel()
    let input = SearchViewModel.Input(
        loadData: .just(()), // 原有初始加载逻辑保留
        searchKeyword: searchTrigger // 传入新增的搜索关键词信号
    )
    let output = viewModel.transform(input)
    
    // 绑定搜索结果到列表/UI
    output.hashtagSearchResult
        .drive(tableView.rx.items(cellIdentifier: "HashtagCell")) { (_, item, cell) in
            // 配置cell显示Hashtag内容即可
        }
        .disposed(by: rx.disposeBag)
}

2. ViewModel层改造:新增Hashtag搜索逻辑

扩展Input/Output结构,新增搜索信号处理、接口调用逻辑:

final class SearchViewModel {
    private let disposeBag = DisposeBag()
    // 新增Hashtag结果模型,按业务需求定义字段即可
    struct HashtagItem {}
    
    struct Input {
        let loadData: Signal<Void>
        let searchKeyword: Signal<String> // 新增:搜索触发的关键词信号
    }
    
    struct Output {
        let result: Signal<String>
        let loadApplyList: PublishRelay<[friends]>
        let hashtagSearchResult: PublishRelay<[HashtagItem]> // 新增:返回搜索结果给VC
    }
    
    func transform(_ input: Input) -> Output {
        let api = SearchAPI()
        let result = PublishSubject<String>()
        let loadApplyList = PublishRelay<[friends]>()
        let hashtagResult = PublishRelay<[HashtagItem]>()
        
        // 原有初始好友列表加载逻辑完全保留
        input.loadData.asObservable().subscribe(onNext: { [weak self] in
            guard let self = self else { return }
            api.getFriend().subscribe(onNext: { (response, statuscode) in
                switch statuscode {
                case .ok:
                    if let response = response {
                        loadApplyList.accept(response)
                    }
                default:
                    print("default")
                }
            }).disposed(by: self.disposeBag)
        }).disposed(by: disposeBag)
        
        // 新增:Hashtag搜索逻辑处理
        input.searchKeyword
            .asObservable()
            .filter { !$0.isEmpty } // 空关键词直接过滤,不发请求
            .map { keyword -> String in
                // 按产品规则做关键词预处理:自动补#、裁剪空格等
                keyword.hasPrefix("#") ? keyword : "#\(keyword.trimmingCharacters(in: .whitespaces))"
            }
            .flatMapLatest { processedKeyword in
                // 调用Hashtag搜索接口,自动取消上一次未完成的请求
                api.searchHashtag(keyword: processedKeyword)
                    .catchAndReturn((nil, .serverError))
            }
            .subscribe(onNext: { (response, statuscode) in
                switch statuscode {
                case .ok:
                    if let items = response {
                        hashtagResult.accept(items)
                    }
                default:
                    result.onNext("搜索失败,请稍后重试")
                }
            })
            .disposed(by: disposeBag)
        
        return Output(
            result: result.asSignal(onErrorJustReturn: ""),
            loadApplyList: loadApplyList,
            hashtagSearchResult: hashtagResult
        )
    }
}

注:需要在SearchAPI中新增searchHashtag方法,入参为预处理后的Hashtag关键词,返回对应的结果和状态码。

3. 可选优化点

  • 结果分页:如果Hashtag搜索结果有分页,可在VC层新增列表滚动到底部的信号,传给ViewModel拼接分页参数
  • 历史搜索:可把用户的搜索关键词通过本地存储,搜索框点击时弹出历史标签
  • 输入联想:如果需要边输入边展示联想的Hashtag,可把searchBtn.rx.tap触发改成searchInput直接触发,保留防抖逻辑即可
  • 错误兜底:搜索失败、空结果的文案统一通过result信号传给VC展示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.06 19:15:07