概要
SwiftのCollectionViewCellの実装中に、Cell内に配置しているUIButtonの形を円形にしようと実装していたところ、なぜかひし形のような形になって型崩れしてしまっていました。
条件
サーバーから取得してきたユーザー情報をCollectionViewCellに入れるというごく単純なものになります。
Swiftファイルとは別に切り出した、『CollectionViewCell.Swift』を作成し、ViewController.SwiftファイルのcellForAtでセル一つずつに情報を入れるという形で実装していました。
実装
コードの状況としては、以下のような感じでした。
final class CollectionViewCell: UICollectionViewCell {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
override func layoutSubviews() {
super.layoutSubviews()
setupLayout()
}
func configure(user: User) {
/// User型の情報をセルのラベル等に入れる
}
}
private extention CollectionViewCell {
func setupLayout() {
/// こんな感じでボタンを円形にする実装をしています。
/// 後半の条件でlayerを使うかは忘れたので無視でお願いします。
sendButton.layer.cornerRadius = sendButton.layer.width / 2
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
final class ViewController: UIViewController {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//MARK: - UICollectionViewDelegate, UICollectionViewDataSource
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
/// セルの数を設定する関数を宣言
func collectionView(_ tableView: UICollectionView, cellForRowAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withIdentifier: CollectionViewCell.identifier, for: indexPath) as! CollectionViewCell
/// ここでViewController内の関数やPresenterを介して情報を取得し、
/// cellで宣言した『configure(user: User)』を用いて、セルを生成しています。
cell.configure(user: User)
return cell
}
/// セルの高さとかを設定する関数を宣言
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
ここまではよくある実装で何の変哲もないと思います。
ただ、僕が実装していたタスク内で、cellの『setupLayout()』内でボタンを円形にしようとしていましたが、なぜか菱形になってしまったのです。毎回というわけでもないのですが、そのcollectionViewCellを使用しているViewに入ると1/2回ぐらいの確率でHishigataViewCellになってしまいます。
自分の引き出し的に、仮説としては大きく3つかなと思いました。
1つ目は、Cell内のlayoutSubviews()がうまく動作していないのかという仮説です。
Appleの公式リファレンスを除いてみると、以下のように書かれていました。
Apple公式リファレンス【layoutSubviews()】
You should not call this method directly. If you want to force a layout update, call the setNeedsLayout() method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded() method.
日本語訳
このメソッドを直接呼び出さないでください。レイアウトの更新を強制する場合は、次の図面の更新の前にメソッドを呼び出してください。ビューのレイアウトをすぐに更新する場合は、メソッドを呼び出します。
基本的に、layoutIfNeeded()やsetNeedsLayout()等も一緒に使わないといけないと思い、関数を追加しましたが、あまり意味がなかったです。
2つ目は、UIButtonの宣言時にdidSetでボタンに影をつける処理をしていることの副作用があるかもという仮説です。
こちらに関しては、didSetの中に記述していることを上述のsetupLayout()の中に移植してビルドしてもびくともしませんでした。
そして最後に3つ目は、よくあるcellの再利用関連のエラーではないかという仮説です。
しかしこれに関しても、以下のようにセル再利用エラーを防ぐ記述をしても解決することが出来ませんでした。
override func prepareForReuse() {
super.prepareForReuse()
}
どうしたものかな....と途方に暮れていました。
解決方法
菱形Viewが発生した状態でViewControllerのヒエラルキーを覗いてみると紫色の警告が出ていましたが特に何もエラー文を表示してくれませんでした。
紫色の警告と言えば、メインスレッドでViewの描画を行うように指示するような警告であることが多いですよね。
そこで、Dispatchiqueue.main.asyncを用いた非同期処理でしばしば警告を回避することもあることを思いまし、まさかと思い以下のように記述するとなんと菱形ViewCellをCellを改修することが出来ました。
final class ViewController: UIViewController {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//MARK: - UICollectionViewDelegate, UICollectionViewDataSource
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ tableView: UICollectionView, cellForRowAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withIdentifier: CollectionViewCell.identifier, for: indexPath) as! CollectionViewCell
/// ここで以下のように記述することで解決しました!!
Dispatchiqueue.main.async {
cell.layoutSubviews()
}
cell.configure(user: User)
return cell
}
}
このように記述することで、紫色の警告も消えて一切菱形にならなかったです!!
さいごに
思わぬところに落とし穴があるので、いろんな可能性を考えて実装することは大事だなと思った瞬間でした。
もし他にもいい解決方法がありましたら、ご教授いただけるとありがたいです!
コメダのブラックサンダーシロノワールほんまにうまい。
参照
Apple公式リファレンス【layoutSubviews()】
セルの再利用
Swift実践入門(文法書)