[Swift]CollectionViewで、PullToRefresh中にTopInsetを変更した時の挙動
概要
UICollectionViewController
で、UIScrolRefresh
を用いた、PullToRefresh
をしている最中に CollectionView.contentInset.top
の値を変更すると、CollectionView
のセルが元の場所に戻らなくなる。
(添付動画では、pullToRefresh完了後に、1つ目のセルが上部にめり込んでいる)
原因
-
conetntInsetAdjustmentBehavior = .never
で、pullToRefresh
の最中にtopInset
の値を変更したこと。 - 試した時の環境(Xcode13.4, iOS15)
解決策
-
pullToRefresh
の最中にtopInset
の値を変更しない。 -
pullToRefresh
中にtopInset
の値を変更するときは、endRefreshing
で引かれる値をあらかじめ足しておく。 -
contentInset.adjustmentBehavior
をデフォルトのautomatic
のままにする。
github)
検証(ソースコード簡単なdemoプロジェクトを作って、何が起きているかを検証する。
ViewDidAppear周り
-
contentInset.adjustmentBehavior
をnever
に設定している。 -
refreshControl
で、pullToRefresh
のときに、self.pullToRefresh
が呼ばれるように設定。 -
contentInset.top
に50
を設定し、上部に余白を持たせている。
//MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setup()
//default is .automatic
collectionView.contentInsetAdjustmentBehavior = .never
printInsetTop(message: "ViewDidAppear")
}
private func setup() {
collectionView.backgroundColor = .white
//Refresh Control
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
refreshControl.addTarget(self, action: #selector(self.pullToRefresh), for: .valueChanged)
collectionView.addSubview(refreshControl)
// set topInset so that we can see the 1st cell entirely
self.setCollectionViewTopInset()
}
private func setCollectionViewTopInset() {
collectionView.contentInset.top = 50
}
// MARK: - Debug
private func printInsetTop(message: String) {
print("# \(message)")
print("+ contentInset: \(collectionView.contentInset.top)")
print("+ adjustedContentInset: \(collectionView.adjustedContentInset.top)")
print("")
}
pullToRefresh周り
-
stardLoading
の中で、topInset
の値をいじる。 -
startLoading
の2秒後に、endLoading
を呼び、pullToRefresh
を終了させる。 -
startLoading
、endLoading
において、topInset
の値をプリントする。
// MARK: - Pull To Refresh
@objc func pullToRefresh() {
startLoading()
DispatchQueue.main.asyncAfter (deadline: .now() + 2) {
self.endLoading()
}
}
private func startLoading() {
printInsetTop(message: "startLoading before modify inset")
setCollectionViewTopInset()
printInsetTop(message: "startLoading after modify inset")
}
private func endLoading() {
printInsetTop(message: "startLoading before endRefreshing")
refreshControl.endRefreshing()
printInsetTop(message: "startLoading after endRefreshing")
}
何が起きていたのか
上記のコードを用いて、pullToRefresh
を行った時の、collectionView
のtopInset
の値をprintしてみると下のようになる。
# ViewDidAppear
+ contentInset: 50.0
# startLoading before modify inset
+ contentInset: 110.0
# startLoading after modify inset
+ contentInset: 50.0
# startLoading before endRefreshing
+ contentInset: 50.0
# startLoading after endRefreshing
+ contentInset: -10.0
-
ViewDidAppear
の時点では、設定した通り50
となっている。 -
pullToRefresh
によって、startLoading
が呼ばれた時点では、110
となっている。 -
startLoading
の中で、topInset=50
と設定したため、値が50
に戻っている。 -
refreshControl.endRefreshing
が呼ばれる前は、50
のまま。 -
refreshControl.endRefreshing
が呼ばれた後に、-10
となっており、これが上部にめり込んだ原因。
考察
-
pullToRefresh
中には、画面上部にloadingIndicator
が表示される。
そのためのスペースを用意するために、pullToRefresh
が始まったタイミングで、collectionView
によって、topInset
の値が変更される。(今回のデモでは、元の値+60
されていた) -
pullToRefresh
が終わる時には、loadingIndicator
のために用意したスペースを元に戻す必要があるので、refreshControl.endRefreshing
が呼ばれるタイミングで、topInset
の値が変更される。(今回のデモでは、-60
されていた) -
pullToRefresh
の途中で、topInset
の値を変更したため、もとに戻らなくなった。- (通常の場合):
start:50
->start Refresh: 50 + 60
->end Refresh: 50 + 60 - 60
->end: 50
- (demoの場合):
start:50
->start Refresh: 50 + 60
->値をセット: 50
->end Refresh: 50 - 60
->end: -10
- (通常の場合):
解決策
pullToRefresh
の最中にtopInset
の値を変更しない
1.-
topInset
以外の値の変更(e.g. bottomInset)なら、この問題は発生しなかった。
pullToRefresh
中にtopInset
の値を変更するときは、endRefreshing
で引かれる値をあらかじめ足しておく
2. -
setCollectionViewTopInset()
の中で、以下のようにする。
collectionView.contentInset.top = 50 + (refreshControl.isRefreshing ? 60 : 0)
contentInset.adjustmentBehavior
をデフォルトのautomatic
のままにする
3. - こうすると、
pullToRefresh
中にtopInset
を変更しても、この問題は発生しない。 -
contentInset.adjustmentBehavior = .automatic
にしたときの、topInset
の値の変化。
# ViewDidAppear
+ contentInset: 50.0
+ adjustedContentInset: 50.0
# startLoading before modify inset
+ contentInset: 50.0
+ adjustedContentInset: 160.0
# startLoading after modify inset
+ contentInset: 50.0
+ adjustedContentInset: 160.0
# startLoading before endRefreshing
+ contentInset: 50.0
+ adjustedContentInset: 160.0
# startLoading after endRefreshing
+ contentInset: 50.0
+ adjustedContentInset: 100.0
Discussion