Плавная анимация в UICollectionView при изменении источника данных

#ios #swift #animation #uicollectionview

#iOS #swift #Анимация #uicollectionview

Вопрос:

У меня есть UISegmentControl, который я использую для переключения источника данных для UICollectionView. Источниками данных являются объекты разных типов.

Например, объекты могут выглядеть следующим образом

 struct Student {
    let name: String
    let year: String
    ...
}
struct Teacher {
    let name: String
    let department: String
    ...
}
  

И в представлении, содержащем CollectionView, был бы код, подобный этому:

 var students = [Student]()
var teachers = [Teachers]()
... // populate these with data via an API

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    if(segmentControl.titleForSegment(at: segmentControl.selectedSegmentIndex) == "Students") {
        return students?.count ?? 0
    } else {
        return teachers?.count ?? 0
    } 
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "personCell", for: indexPath) as! PersonCell
    if(segmentControl.titleForSegment(at: segmentControl.selectedSegmentIndex)! == "Students") {
        cell.title = students[indexPath.row].name
        cell.subtitle = students[indexPath.row].year
    } else {
        cell.title = teachers[indexPath.row].name
        cell.subtitle = teachers[indexPath.row].subject
    }
    return cell
}

@IBAction func segmentChanged(_ sender: AnyObject) {
    collectionView.reloadData()
}
  

Это правильно переключает между двумя источниками данных, однако это не анимирует изменение. Я пробовал это:

 self.collectionView.performBatchUpdates({
    let indexSet = IndexSet(integersIn: 0...0)
    self.collectionView.reloadSections(indexSet)
}, completion: nil)
  

Но это просто сбой (я думаю, это потому, что performBatchUpdates путается в том, что удалять и что добавлять).

Есть ли какой-нибудь простой способ заставить это работать, не имея отдельного массива, хранящего текущие элементы в CollectionView, или это единственный способ заставить это работать плавно?

Заранее большое спасибо!

Ответ №1:

Если пользовательский интерфейс вашей ячейки выглядит одинаково из разных источников данных, вы можете абстрагировать ViewModel от вашего источника данных, например:

 struct CellViewModel {
    let title: String
    let subTitle: String
    ...
}
  

Затем каждый раз, когда вы получаете данные из API, динамически генерируйте ViewModel

     var students = [Student]()
    var teachers = [Teachers]()
    ... // populate these with data via an API

    var viewModel = [CellViewModel]()
    ... // populate it from data above by checking currently selected segmentBarItem

    if(segmentControl.titleForSegment(at: segmentControl.selectedSegmentIndex)! == "Students") {
        viewModel = generateViewModelFrom(students)
    } else {
        viewModel = generateViewModelFrom(teachers)
    }
  

Таким образом, вы всегда сохраняете один массив источников данных в своем UICollectionView.

 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return viewModel?.count ?? 0
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "personCell", for: indexPath) as! PersonCell
    cell.title = viewModel[indexPath.row].title
    cell.subtitle = viewModel[indexPath.row].subTitle
    return cell
}

@IBAction func segmentChanged(_ sender: AnyObject) {
    collectionView.reloadData()
}
  

Затем попробуйте performBatchUpdates:

 self.collectionView.performBatchUpdates({
    let indexSet = IndexSet(integersIn: 0...0)
    self.collectionView.reloadSections(indexSet)
}, completion: nil)