'UICollectionView + UIRefreshControl animation differences to Apple Mail app
I am implementing a ViewController with an UICollectionView + UIRefreshControl and large titles. Its pull to refresh animation looks different compared to the ones from Apple Apps like Mail.
In the mail app the UIActivityIndicator is somehow "building up" based on how far the user has dragged, whereas in my sample implementation the indicator is fading in. I already tried to play with the top anchors or different ways to trigger the refresh but I cannot get the "native" animation of the UIRefreshControl. At the following link, 2 videos are shown, one showing the mail app animation, the other one my custom view implementations: https://imgur.com/a/ekfmZBu
Are there any hidden configurations or maybe some other ways to implement this "native" animation.
What I don't know though is whether the Apple mail app is using an UICollectionView. I also tried it with a UITableView and was also facing the "fading in" animation rather than this "growing" animation.
Maybe someone already had experienced this and knows what could be the issue.
Any help is appreciated.
EDIT:
This is the code of the View Controller that is creating the UICollectionView. It also contains some references to a ViewModel and some custom types, but these should not interfere with the UI differences I see.
class CollectionViewTestViewController: UIViewController {
// MARK: - UI Properties
private lazy var collectionView = makeCollectionView()
private lazy var dataSource = makeDataSource()
private(set) lazy var refreshControl: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.addAction(
UIAction { [weak self] _ in
self?.viewModel.refresh()
}, for: .valueChanged
)
return refreshControl
}()
// MARK: - ViewModel
private let viewModel: CollectionViewTestViewModel
// MARK: - Combine
private let cancelBag = CancelBag()
// MARK: - Initializer
init(viewModel: CollectionViewTestViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
self.title = "CollectionView"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Lifecylce
override func viewDidLoad() {
super.viewDidLoad()
self.configureUI()
self.bindToViewModel()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
self.navigationController?.navigationBar.prefersLargeTitles = true
}
}
// MARK: - UI Setup
extension CollectionViewTestViewController {
private func configureUI() {
view.backgroundColor = .systemBackground
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
])
}
}
// MARK: - Collection View Handling
extension CollectionViewTestViewController {
// MARK: - UICollectionView
func makeCollectionView() -> UICollectionView {
let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
let layout = UICollectionViewCompositionalLayout.list(using: config)
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.translatesAutoresizingMaskIntoConstraints = false
view.refreshControl = refreshControl
view.contentInset = .init(top: 16, left: 0, bottom: 0, right: 0)
return view
}
// MARK: - Cell Registration
func makeCellRegistration() -> UICollectionView.CellRegistration<UICollectionViewListCell, Contact> {
UICollectionView.CellRegistration { cell, indexPath, contact in
// Configuring each cell's content:
var config = cell.defaultContentConfiguration()
config.text = contact.name
config.secondaryText = "\(contact.birthday)"
cell.contentConfiguration = config
// Showing a disclosure indicator as the cell's accessory:
cell.accessories = [.disclosureIndicator()]
}
}
// MARK: - Data Source
func makeDataSource() -> UICollectionViewDiffableDataSource<Section, Contact> {
let cellRegistration = makeCellRegistration()
return UICollectionViewDiffableDataSource<Section, Contact>(
collectionView: collectionView,
cellProvider: { view, indexPath, item in
view.dequeueConfiguredReusableCell(
using: cellRegistration,
for: indexPath,
item: item
)
}
)
}
}
// MARK: - MVVM
extension CollectionViewTestViewController {
private func bindToViewModel() {
self.viewModel.$contacts
.receive(on: RunLoop.main)
.sink { [weak self] sections in
switch sections {
case .initial:
break
case .loading:
self?.collectionView.refreshControl?.beginRefreshing()
case let .value(data):
self?.collectionView.refreshControl?.endRefreshing()
self?.updateList(with: data)
case let .error(error):
self?.collectionView.refreshControl?.endRefreshing()
}
}
.store(in: self.cancelBag)
}
}
// MARK: - Data Updating
extension CollectionViewTestViewController {
func updateList(with data: [Contact]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Contact>()
snapshot.appendSections(Section.allCases)
snapshot.appendItems(data, toSection: .all)
dataSource.apply(snapshot)
}
}
Solution 1:[1]
You don't have to work with top anchors. The "growing" animation is a result of the valueChanged event of UIRefreshControl. Add a target to your UIRefreshControl and set it to your UICollectionView like this:
refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged)
collectionView.refreshControl = refreshControl
And for the "refresh" function:
@objc func refresh(sender: UIRefreshControl) {
// End refreshing at some point
self.refreshControl.endRefreshing()
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | yjoon |
