如何在 RxDataSource 中将新部分反映到 UICollectionView?

Posted

技术标签:

【中文标题】如何在 RxDataSource 中将新部分反映到 UICollectionView?【英文标题】:How to reflect the new section to UICollectionView in RxDataSource? 【发布时间】:2019-01-29 15:14:57 【问题描述】:

我正在使用 VIPER 架构构建一个带有 RxSwift 和 RxDataSource 的 ios 应用程序。我想更改 UICollectionView 的内容,因为演示者的值发生变化(当用户在 searchBar 中输入一些字母时,collectionView 应该显示所有以字母开头的用户的个人资料),但它不能作为我想要它。

通过尝试debug() 函数,在我在searchBar 中输入一些字母后,ViewController 中presenter.section 的值(它保存了CollectionView 的内容)发生了变化。但是,collectionView 并未反映更改。 以下是代码的主要部分。

ViewController的代码

override func viewDidLoad() 

    super.viewDidLoad()
    self.view.backgroundColor = .white

    self.presenter.section
        .drive(self.searchedFriendsCollectionView.rx.items(dataSource: self.dataSource))
        .disposed(by: self.disposeBag)

    self.searchedFriendsCollectoinView.rx
        .setDelegate(self)
        .disposed(by: self.disposeBag)

Presenter 代码

init(view: SearchFriendsViewInterface, interactor: 
SearchFriendsInteractorInterface, wireframe: 
SearchFriendsWireframeInterface) 

    self.view = view
    self.interactor = interactor
    self.wireframe = wireframe

    let allUsers = self.interactor.allUsers().asObservable()

    self.view.searchText
        .debounce(0.5)
        .filterNil()
        .distinctUntilChanged()
        .filterEmpty()
        .asObservable()
        .flatMap  text -> Observable<[SearchFriendsSection]> in
            // get all users starting with "text"
            allUsers.mapSections(query: text)
        
        .bind(to: self.section)
        .disposed(by: self.disposeBag)
 

extension Observable where E == [User] 

fileprivate func mapSections(query: String) -> Observable<[SearchFriendsSection]> 

    return self.map 
            $0.filter  $0.userDisplayName.hasPrefix(query) || $0.username.hasPrefix(query) 
        
        .map  users -> [SearchFriendsSection] in

            let items = users.map  user in
                SearchFriendsItem(icon: user.profileImageURL, displayName: user.userDisplayName)
            
            return [SearchFriendsSection(items: items)]
        
    

我如何定义dataSource

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) 

    self.searchedFriendsCollectoinView = UICollectionView(frame: .init(), collectionViewLayout: self.searchedFriendsCollectionViewFlowLayout)
    let dataSource = RxCollectionViewSectionedReloadDataSource<SearchFriendsSection> (configureCell: (_, collectionView, indexPath, item) -> UICollectionViewCell in
        collectionView.register(SearchFriendsCell.self, forCellWithReuseIdentifier: "SearchFriendsCell")
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SearchFriendsCell", for: indexPath) as! SearchFriendsCell
        cell.item = item
        return cell
    )
    self.dataSource = dataSource
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)

ViewController 有 Presenter 的实例,而 Presenter 有一个名为 section 的实例。这个section 包含用户名以特定字母开头的用户列表。

你能帮帮我吗?如果我有任何不清楚的地方,请在评论中告诉我。

更新:debug() 的输出

2019-02-01 10:22:27.610: values -> subscribed
2019-02-01 10:22:27.611: values -> Event next([])
--after typed in some letters in the searchBar--
2019-02-01 10:22:41.494: values -> Event 
next([AppScreen.SearchFriendsSection(items: 
[AppScreen.SearchFriendsItem(....), 
AppScreen.SearchFriendsItem(....), 
AppScreen.SearchFriendsItem(....)])])

【问题讨论】:

如果您将debug() 调用放在绑定/驱动/订阅表视图本身的数据源之前。它的输出是什么? Daniel T. 看起来self.dataSource 在我输入了一些字母后也没有改变,它被处理掉了。但是self.dataSource跟UICollectionView的内容有关系吗? 如果驱动/绑定到你的数据源的链被释放,那么它就不能再发出新的值了。您的链中的 Observable 正在发送停止事件(完成/错误),或者正在从绑定/订阅/驱动器调用一次性的 dispose()。 抱歉self.dataSource 没有改变(因为它是let)但是当我在ViewController 中将debug() 放在drive() 之前,新元素被发射了。你知道为什么 CollectionView 的内容没有改变吗? 我想我不是很清楚...在视图控制器中的self.presenter.section 行和.drive(self.searchedFriendsCollectionView.rx.items(dataSource: self.dataSource)) 行之间放置一个.debug("values"),并将该调试的输出发布到您的问题。 【参考方案1】:

我发现了你的问题。您从未将集合视图添加到主视图中。如果集合视图没有超级视图,或者它的大小为零,则它不会自行加载。

将这两行放在你的 viewDidLoad 中:

searchedFriendsCollectionView.frame = view.bounds
view.addSubview(searchedFriendsCollectionView)

当你在它的时候,你应该把这条线从你的配置闭包移到 viewDidLoad:

searchedFriendsCollectionView.register(SearchFriendsCell.self, forCellWithReuseIdentifier: "SearchFriendsCell")

没有理由每次系统请求一个单元格时都调用它。

【讨论】:

对不起,我忘了把代码放在问题中,但我使用 FlexLayout 设置了 CollectionView。无论如何,当我用你的代码替换 FlexLayout 代码时,情况看起来并没有改变。当我在init(nibname) 中放置断点时,每次我在搜索栏中输入字母时都会调用它。另外,cell.item 的值似乎已更新......也许问题出在SearchFriendsCell 所以我会寻找它。 是的,问题就在那里。对不起。你的回答对我有帮助,因为我对 rxswift 很着迷,直到我看到你的帖子才认为原因出在其他地方。非常感谢。【参考方案2】:

只是想知道您如何定义self.interactor.allUsers()allUsers.mapSections(query: text)

您是否致电onNext() 通知观察者序列中有一个新元素。在您映射部分或获取所有用户之后?

如果你真的想使用flatMap。我认为您应该改用 flatMapLatest

参考: RxSwift map and flatMap difference

更新:在这种情况下,您需要在 mapSections 中创建一个新的 observable,它始终会生成 [SearchFriendsSection] 并将其发送给观察者

 return Observable.create  observer in
        let filteredSections = ...
        observer.on(.next(filteredSections))
        observer.on(.completed)
        return Disposables.create()
    

【讨论】:

感谢您的评论。我已经把mapSection()的代码放在问题里了,请看一下!我认为元素是按顺序通知的。当我使用debug() 检查时,当我输入不同的字母时,ViewController 中的self.presenter.section 每次都会收到不同的元素。 在 mapSections 中,您只需使用 map 来转换 allUsers 发出的用户。您返回的可观察对象不会发出任何项目。您需要创建一个始终生成 [SearchFriendsSection] 的新可观察对象并将其发送给观察者。你可以在我更新的答案中看到这个例子,如果你打算这样做,你最好在 Interactor 中定义函数,比如findUsersBy() 是的,像你说的那样使用findUsersBy() 这样的方法肯定会更好。谢谢。但是,我认为它正在发布新项目。例如,如果我在 searchBar 上没有输入任何内容,元素为空,然后我在 searchBar 中输入“A”,则返回用户名为 Alex 的元素,self.presenter.section 根据debug() 接收该用户.这不意味着新元素被发射了吗?老实说,我对 RxSwift 有点陌生,所以也许这是一个简单的问题 有趣的是,如果你能收到这些物品。你怎么把你的debug(),你是这样使用self.presenter.section.debug().bind _ in .disposed(by: disposeBag)吗?但是,如果您的数据流没有任何问题,则根本原因应该是dataSource。你如何定义数据源? 是的,我仍然使用该流程。我刚刚将定义数据源的代码放在问题中,所以请看一下!【参考方案3】:

好的,最后我解决了这个问题,这与 Rxswift 没有直接关系。我是用FlexLayout来布局一切的,在CollectionViewCell中,我没有把代码放在下面。

override func layoutSubviews() 

    super.layoutSubviews()
    self.contentView.flex.layout()

没有这个代码就没有理由更新视图,因为没有这个代码 FlexLayout 不会执行布局。

【讨论】:

以上是关于如何在 RxDataSource 中将新部分反映到 UICollectionView?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 RxDatasource 在 UICollectionView 中创建具有多个标题的多个部分

如何解决气旋复杂性? - RxDataSource RxSwift - SwiftLint

如何在 C# 中将数组的一部分复制到另一个数组?

用户在表格视图控制器中将行/部分添加到静态单元格

如何在 COBOL 的链接部分中使用外部变量并将值从它传递到新模块并写入我的新输出文件

如何在 Userdefaults (SwiftUI) 中将新元素附加到数组