#ios #swift
#iOS #быстрый
Вопрос:
У меня странная проблема в UIViewController, который пользователь использует для поиска GIF.
По сути, есть 2 проблемы:
- Пользователь должен дважды ввести один и тот же поисковый запрос, прежде чем UICollectionView запустит метод источника данных cellForRowAt после выполнения вызова
reloadData()
. - После того, как вы введете поисковый запрос в первый раз,
heightChanged()
он вызывается, ноself.GIFCollectionView.collectionViewLayout.collectionViewContentSize.height
возвращается как 0, хотя я подтвердил, что данные поступают обратно с сервера. При втором вводе поискового запроса высота будет ненулевым значением, а в представлении коллекции будут показаны ячейки.
Вот пример того, как я должен получить данные, чтобы они отображались:
- Запустите приложение, перейдите к этому UIViewController
- Введите поисковый запрос (т. е. «бейсбол»)
- Ничего не отображается (даже несмотря
reloadData()
на то, что был вызван, и новые данные находятся в модели представления). - Удалите символ из поискового запроса (т. е. «бейсбол»)
- Введите отсутствующий символ (т. е. «бейсбол»)
- UICollectionView обновляется с помощью вызова
reloadData()
, а затем вызываетcellForRowAt:
.
Вот весь контроллер представления:
import UIKit protocol POGIFSelectViewControllerDelegate: AnyObject { func collectionViewHeightDidChange(_ height: CGFloat) func didSelectGIF(_ selectedGIFURL: POGIFURLs) } class POGIFSelectViewController: UIViewController { //MARK: - Constants private enum Constants { static let POGIFCollectionViewCellIdentifier: String = "POGIFCollectionViewCell" static let verticalPadding: CGFloat = 16 static let searchBarHeight: CGFloat = 40 static let searchLabelHeight: CGFloat = 24 static let activityIndicatorTopSpacing: CGFloat = 10 static let gifLoadDuration: Double = 0.2 static let gifStandardFPS: Double = 1/30 static let gifMaxDuration: Double = 5.0 } //MARK: - Localized Strings let localizedSearchGIFs = PALocalizedStringFromTable("RECOGNITION_IMAGE_SELECTION_GIF_BODY_TITLE", table: "Recognition-Osiris", comment: "Search GIFs") as String //MARK: - Properties var viewModel: POGIFSearchViewModel? var activityIndicator = MDCActivityIndicator() var gifLayout = PAGiphyCellLayout() var selectedGIF: POGIFURLs? //MARK: - IBOutlet @IBOutlet weak var GIFCollectionView: UICollectionView! @IBOutlet weak var searchGIFLabel: UILabel! { didSet { self.searchGIFLabel.text = self.localizedSearchGIFs } } @IBOutlet weak var searchField: POSearchField! { didSet { self.searchField.delegate = self } } @IBOutlet weak var activityIndicatorContainer: UIView! //MARK: - Delegate weak var delegate: POGIFSelectViewControllerDelegate? //MARK: - View Lifecycle Methods override func viewDidLoad() { super.viewDidLoad() self.setupActivityIndicator(activityIndicator: self.activityIndicator, activityIndicatorContainer: self.activityIndicatorContainer) self.viewModel = POGIFSearchViewModel(data: PAData.sharedInstance()) if let viewModel = self.viewModel { viewModel.viewDelegate = self viewModel.viewDidBeginLoading() } self.gifLayout.delegate = self self.gifLayout.isAXPGifLayout = true; self.GIFCollectionView.collectionViewLayout = self.gifLayout self.GIFCollectionView.backgroundColor = .orange } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // This Patch is to fix a bug where GIF contentSize was not calculated correctly on first load. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() .seconds(1)) { self.viewModel?.viewDidBeginLoading() } } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() self.heightChanged() } //MARK: - Helper Methods func heightChanged() { guard let delegate = self.delegate else { return } let height = self.GIFCollectionView.collectionViewLayout.collectionViewContentSize.height Constants.verticalPadding * 3 Constants.searchLabelHeight Constants.searchBarHeight activityIndicatorContainer.frame.size.height Constants.activityIndicatorTopSpacing print("**** Items in Collection View -gt; self.viewModel?.gifModel.items.count: (self.viewModel?.gifModel.items.count)") print("**** self.GIFCollectionView.collectionViewLayout.collectionViewContentSize.height: (self.GIFCollectionView.collectionViewLayout.collectionViewContentSize.height); height: (height)") delegate.collectionViewHeightDidChange(height) } func reloadCollectionView() { self.GIFCollectionView.collectionViewLayout.invalidateLayout() self.GIFCollectionView.reloadData() self.GIFCollectionView.layoutIfNeeded() self.heightChanged() } func imageAtIndexPath(_ indexPath: IndexPath) -gt; UIImage? { guard let previewURL = self.viewModel?.gifModel.items[indexPath.row].previewGIFURL else { return nil } var loadedImage: UIImage? = nil let imageManager = SDWebImageManager.shared() imageManager.loadImage(with: previewURL, options: .lowPriority, progress: nil) { (image: UIImage?, data: Data?, error: Error?, cacheType: SDImageCacheType, finished: Bool, imageURL: URL?) in loadedImage = image } return loadedImage } func scrollViewDidScrollToBottom() { guard let viewModel = self.viewModel else { return } if viewModel.viewDidSearchMoreGIFs() { self.activityIndicator.startAnimating() } else { self.activityIndicator.stopAnimating() } } } extension POGIFSelectViewController: POSearchFieldDelegate { func searchFieldTextChanged(text: String?) { guard let viewModel = self.viewModel else { return } viewModel.viewDidSearchGIFs(withSearchTerm: text) } } extension POGIFSelectViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -gt; UICollectionViewCell { print("**** CELL FOR ROW AT -gt; self.viewModel?.gifModel.items.count: (self.viewModel?.gifModel.items.count)") let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constants.POGIFCollectionViewCellIdentifier, for: indexPath) as! POGIFCollectionViewCell guard let previewURL = self.viewModel?.gifModel.items[indexPath.row].previewGIFURL else { return cell } var cellState: POGIFCollectionViewCell.CellState = .dimmedState if self.selectedGIF == nil { cellState = .defaultState } else if (self.selectedGIF?.previewGIFURL?.absoluteString == previewURL.absoluteString) { cellState = .selectedState } cell.setupUI(withState: cellState, URL: previewURL) { [weak self] () in UIView.animate(withDuration: Constants.gifLoadDuration) { guard let weakSelf = self else { return } weakSelf.GIFCollectionView.collectionViewLayout.invalidateLayout() } } if cell.GIFPreviewImageView.animationDuration gt; Constants.gifMaxDuration { cell.GIFPreviewImageView.animationDuration = Constants.gifMaxDuration } cell.backgroundColor = .green return cell } func numberOfSections(in collectionView: UICollectionView) -gt; Int { return 1 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -gt; Int { guard let viewModel = self.viewModel else { return 0 } return viewModel.gifModel.items.count } } extension POGIFSelectViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let selectedGIF = self.viewModel?.gifModel.items[indexPath.row], let delegate = self.delegate else { return } self.selectedGIF = selectedGIF delegate.didSelectGIF(selectedGIF) self.reloadCollectionView() } } extension POGIFSelectViewController: POGIFSearchViewModelToViewProtocol { func didFetchGIFsWithSuccess() { self.activityIndicator.stopAnimating() print("**** didFetchGIFsWithSuccess() -gt; about to reload collection view") self.reloadCollectionView() } func didFetchGIFsWithError(_ error: Error!, request: PARequest!) { self.activityIndicator.stopAnimating() } } extension POGIFSelectViewController: PAGiphyLayoutCellDelegate { func heightForCell(givenWidth cellWidth: CGFloat, at indexPath: IndexPath!) -gt; CGFloat { guard let image = self.imageAtIndexPath(indexPath) else { return 0 } if (image.size.height lt; 1 || image.size.width lt; 1 || self.activityIndicator.isAnimating) { return cellWidth } let scaleFactor = image.size.height / image.size.width let imageViewToHighlightedViewSpacing: CGFloat = 4 // this number comes from 2 * highlightedViewBorderWidth from POGIFCollectionViewCell return cellWidth * scaleFactor imageViewToHighlightedViewSpacing } func heightForHeaderView() -gt; CGFloat { return 0 } }
Вы увидите, что heightChanged()
метод вызывает метод делегата. Этот метод находится в другом UIViewController:
func collectionViewHeightDidChange(_ height: CGFloat) { self.collectionViewHeightConstraint.constant = height }
Итак, я не могу понять, почему мне нужно либо удалить символ из поискового запроса, либо повторно добавить его, чтобы данные обновились, даже если самый первый вызов заполнил модель представления новыми данными.
Это странно. Пожалуйста, помогите.
Комментарии:
1. Первый комментарий заключается в том, что в этом коде многое происходит, из-за чего трудно определить, в чем проблема. Разобрать кое-что из этого может помочь. Вызов функции reloadData() не означает, что данные будут загружаться. Хотя в коде это не очевидно, я бы ожидал, что загрузка данных будет асинхронной, и, вероятно, поэтому ее нет в первый раз. Загрузка данных должна управляться обработчиком завершения при сетевом вызове, а не VC.
2. Спасибо @flanker, именно это делает метод делегата didFetchGIFsWithSuccess (). Он вызывается внутри другого объекта, когда сетевой запрос успешно извлек GIF-файлы. Итак, это уже делается.
3. Непонятно: где именно обновляется
viewModel
и когда вызываетсяreloadData()
. Действительно ли они последовательны в одной и той же теме? Поскольку это связано с пользовательским интерфейсом,reloadData()
его необходимо вызывать в основном потоке, но также необходимо обновить модель (обработку следующей модели можно выполнить в фоновом потоке, но не устанавливать ее, так как методы делегирования пользовательского интерфейса будут полагаться на нее.