Как отразить новый раздел в UICollectionView в RxDataSource?

Я создаю приложение iOS с RxSwift и RxDataSource, используя архитектуру VIPER. Я хочу изменить содержимое UICollectionView по мере изменения значения ведущего (поскольку пользователь ввел некоторые буквы в searchBar, collectionView должен отображать профиль всех пользователей, который начинается с букв), но это не работает как Я этого хотел.

При попытке использовать функцию debug() значение presenter.section в ViewController (которое содержит содержимое 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)
}

код ведущего

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(....)])])

person joey22    schedule 29.01.2019    source источник
comment
Если вы поместите вызов debug() непосредственно перед привязкой/приводом/подпиской на сам источник данных табличного представления. Каков его выход?   -  person Daniel T.    schedule 29.01.2019
comment
Daniel T. похоже self.dataSource не изменился даже после того, как я набрал несколько букв, он был утилизирован. Но имеет ли self.dataSource какое-то отношение к содержимому UICollectionView?   -  person joey22    schedule 30.01.2019
comment
Если цепочка, которая управляет/привязана к вашему источнику данных, удалена, она больше не может выдавать новые значения. Либо Observable в вашей цепочке отправляет событие остановки (complete/error), либо что-то вызывает dispose() на одноразовом из bind/subscribe/drive.   -  person Daniel T.    schedule 30.01.2019
comment
Извините, self.dataSource не изменился (потому что это let), но когда я поставил debug() прямо перед drive() в ViewController, был создан новый элемент. У вас есть идеи, почему содержимое CollectionView не меняется?   -  person joey22    schedule 31.01.2019
comment
Думаю, я не совсем понятен... Поместите .debug("values") между строкой self.presenter.section и строкой .drive(self.searchedFriendsCollectionView.rx.items(dataSource: self.dataSource)) в контроллере представления и опубликуйте вывод этой отладки в своем вопросе.   -  person Daniel T.    schedule 31.01.2019
comment
Это то, что я сделал. Я поместил вывод в вопрос, поэтому, пожалуйста, посмотрите!   -  person joey22    schedule 01.02.2019
comment
Поставьте точку останова на строке collectionView.dequeueReusableCell.... Звонят?   -  person Daniel T.    schedule 01.02.2019
comment
@ joey22, вы должны официально принять ответ как принятый ответ.   -  person Andreas ZUERCHER    schedule 26.04.2021


Ответы (3)


Я нашел вашу проблему. Вы никогда не добавляли представление коллекции к основному представлению. Если представление коллекции не имеет суперпредставления или имеет нулевой размер, оно не загружает себя.

Поместите эти две строки в свой viewDidLoad:

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

Пока вы это делаете, вы должны переместить эту строку из замыкания configure в viewDidLoad:

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

Нет причин вызывать это каждый раз, когда система запрашивает ячейку.

person Daniel T.    schedule 01.02.2019
comment
Извините, я забыл указать код в вопросе, но я установил CollectionView с помощью FlexLayout. В любом случае, не похоже, что ситуация изменилась, когда я заменил код FlexLayout на ваш. Когда я ставлю точку останова в init(nibname), она вызывается каждый раз, когда я набираю буквы в строке поиска. Кроме того, похоже, что значение cell.item обновлено .... возможно, проблема в SearchFriendsCell, поэтому я поищу его. - person joey22; 01.02.2019
comment
И да, проблема была там. Мне жаль. Ваш ответ помог мне, потому что я был одержим rxswift и не думал, что причина в других частях, пока не увидел ваш пост. Большое спасибо. - person joey22; 01.02.2019

Просто интересно, как вы определяете self.interactor.allUsers() и allUsers.mapSections(query: text)

Вы вызвали onNext(), чтобы уведомить наблюдателя о новом элементе в последовательности. после того, как вы сопоставили разделы или получили всех пользователей?

Если вы действительно хотите использовать flatMap. Я думаю, вам следует использовать flatMapLatest вместо этого

Ссылка: разница между картой RxSwift и flatMap

Обновлено: в этом случае вам нужно создать новую наблюдаемую в mapSections, которая всегда генерирует [SearchFriendsSection] и отправляет ее наблюдателю.

 return Observable.create { observer in
        let filteredSections = ...
        observer.on(.next(filteredSections))
        observer.on(.completed)
        return Disposables.create()
    }
person AIex H.    schedule 29.01.2019
comment
Спасибо за комментарий. Я указал код mapSection() в вопросе, посмотрите, пожалуйста! Я думаю, что элемент уведомлен в последовательности. self.presenter.section в ViewController каждый раз получал разные элементы, когда я вводил разные буквы, когда проверял с помощью debug(). - person joey22; 30.01.2019
comment
В mapSections вы просто используете карту для преобразования пользователей, испускаемых allUsers. Наблюдаемый объект, который вы возвращаете, не будет испускать какой-либо элемент. Вам нужно создать новый наблюдаемый объект, который всегда генерирует [SearchFriendsSection] и отправляет его наблюдателю. Вы можете увидеть пример в моем обновленном ответе, и вам лучше определить функцию в Interactor, например findUsersBy(), если вы собираетесь это сделать. - person AIex H.; 30.01.2019
comment
Да, определенно лучше поставить метод типа findUsersBy(), как вы сказали.. Спасибо. Тем не менее, я думаю, что он испускает новый элемент. Например, если я ничего не набрал в строке поиска, элемент пуст, а затем я набрал A в строке поиска, элемент с пользователем с именем Alex возвращается, и self.presenter.section получает его в соответствии с debug(). Разве это не означает, что новый элемент испускается? Честно говоря, я новичок в RxSwift, поэтому, возможно, это простой вопрос. - person joey22; 31.01.2019
comment
Интересно, если вы можете получить предметы. Как вы ставите debug(), используете ли вы вот так self.presenter.section.debug().bind { _ in }.disposed(by: disposeBag)? Однако, если у вашего потока данных нет проблем, основной причиной должна быть ошибка dataSource. Как вы определяете источник данных? - person AIex H.; 31.01.2019
comment
да, я до сих пор использую этот поток. Я только что поместил код, определяющий источник данных, в вопрос, поэтому, пожалуйста, взгляните! - person joey22; 01.02.2019

Хорошо, наконец, я решил это, и это не имело прямого отношения к Rxswift. Я использовал FlexLayout для компоновки всего, а в CollectionViewCell я не помещал код ниже.

override func layoutSubviews() {

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

Без этого кода не было причин обновлять представление, потому что FlexLayout не выполняет макет без этого кода.

person joey22    schedule 01.02.2019