質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

898閲覧

tableViewでスクロールすると、レイアウトがされいないcellが出てくる場合がある

yoshiSwift

総合スコア34

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2019/12/02 13:52

発生している問題・エラーメッセージ

TableViewを使用したSNSアプリを作ろうとしており、機能としてタグボタンを作っています。

カスタムcell内にカスタムビューを4つaddSubView()しています。
①ユーザーの画像と名前を表示するview
②アーティストの画像と名前、曲名を表示するView
③タグを表示するView
④良いね数を表示するView

問題が発生しているのは③です。
③には、さらにUIButtonがaddSubView()されています。
そのUIButtonのレイアウトを、カスタムView内で以下のように決めているのですが、
TableViewをスクロールすると、そのレイアウトが効かなかったり、効いたりという状況が発生してしまいます。
イメージ説明

また、激しくスクロールすると、アプリが落ちてしまいます。

どのように解決したら良いのでしょうか、教えていただけると幸いです。

//ここでは、タグのボタンの大きさを調整しています。 func adjustSize (tag : UIButton, height: CGFloat) { //adjustBoldFontSizeForHeight(height:)は、heightに適したフォントサイズを自身に設定します。 tag.adjustBoldFontSizeForHeight(height: height) //設定されたフォントサイズ、テキストの長さをもとにtag: UIButton の横幅を決定します。 //こうすることで、UIButtonのタイトルが省略されることなく表示されます。 let attributes = [NSAttributedString.Key.font: tag.titleLabel?.font] let textWidth = tag.titleLabel!.text!.size(withAttributes: attributes).width tag.frame.size = .init(width: textWidth, height: height) }
override func layoutSubviews() { super.layoutSubviews() //tagは0~4つまで作成されます。 //ここで実現したいレイアウトは、タグ(UIButton)が親ビューの左端から順に設定されていく感じです。 //自身の横幅を超えた際に、2段目に行くようにUIButtonの高さは自身の高さの40%に設定。 //また、自身の横幅を超える可能性のある3つめからは、Bool値を使って判断しています。 let tagButtonHeight = self.frame.height * 0.4 let tagViewWidth = self.frame.width * 0.9 let margin = self.frame.width * 0.05 var previousWidth: CGFloat = 0 guard let first = firstTag else { return } adjustSize(tag: first, height: tagViewHeight) first.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true first.topAnchor.constraint(equalTo: self.topAnchor).isActive = true previousWidth += first.frame.width + margin guard let second = secondTag else { return } adjustSize(tag: second, height: tagViewHeight) second.leadingAnchor.constraint(equalTo: first.trailingAnchor, constant: margin).isActive = true second.topAnchor.constraint(equalTo: self.topAnchor).isActive = true previousWidth += second.frame.width + margin guard let third = thirdTag else { return } adjustSize(tag: third, height: tagViewHeight) if previousWidth + third.frame.width < tagViewWidth { third.leadingAnchor.constraint(equalTo: second.trailingAnchor, constant: margin).isActive = true third.topAnchor.constraint(equalTo: self.topAnchor).isActive = true previousWidth += third.frame.width + margin thirdIsOver = false } else { third.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true third.topAnchor.constraint(equalTo: self.centerYAnchor).isActive = true previousWidth += third.frame.width + margin thirdIsOver = true } guard let fourth = fourthTag else { return } adjustSize(tag: fourth, height: tagViewHeight) if thirdIsOver! { fourth.leadingAnchor.constraint(equalTo: third.trailingAnchor, constant: margin).isActive = true fourth.topAnchor.constraint(equalTo: self.centerYAnchor).isActive = true } else if previousWidth + fourth.frame.width < tagViewWidth { fourth.leadingAnchor.constraint(equalTo: third.trailingAnchor, constant: margin).isActive = true } else { fourth.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true fourth.topAnchor.constraint(equalTo: self.centerYAnchor).isActive = true } }

イメージ説明

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

よくコードが見辛くて、意味が理解できていないのですが、
CustomCellのクラスを作成したのですか?
(class ***Cell: UITableViewCell {} とういコードが見当たらない)
Storyboardを利用していますか???
(40%などあるけど、実際にどうなっているのか、不明)

おそらくですが、override func layoutSubviews() {}
はViewControllerが作成された1回しか読みこまれないので、
func tableView cellForRowAt indexPath: を利用して、
位置を調整するようにコードを書き直さないと
使いまわされたcellに違うcellの設定がそのまま適用され、
表示が合ってたり、合ってなかったりするのが原因だと思います。
そして、それが無理のある設定になってしまった時に落ちることになります。

下記に例と注意点

Swift

1// Data 2struct CDData { 3 let rank: Int 4 let title: String 5 let artist: String 6} 7 8 9// Custom cell 10import UIKit 11 12class CustomCell: UITableViewCell { 13 @IBOutlet weak var titleLabel: UILabel 14 @IBOutlet weak var artistLabel: UILabel 15 16 override func layoutSubviews() { 17 super.layoutSubviews() 18 print("CustomCell, layoutSubviews") 19 self.label.textColor = UIColor.red // <-これもprintで見てもらえればわかると思いますが、、1回しか呼ばれない(再利用時は呼ばれない) 20 } 21 22 func bind1(data: CDData) { 23 self.titleLabel.text = data.title 24 self.artistLabel.text = data.artist 25 if data.rank == 1 { 26 self.titleLabel.textColor = UIColor.red 27 } else { 28 self.titleLabel.textColor = UIColor.black // <-こういうのを忘れると、予期しないところ(再利用されたところ)で色が赤になってしまう。 29 } 30 } 31 32 // ちなみに自分なら、下記のように切り分けてどこを変更する時はどこを見ればいいかわかるようにします。 33 func bind2(data:CDData) { 34 makeUserItems() 35 makeArtistItems(cdData: data) 36 makeTag() 37 makeGood() 38 } 39 40 private func makeUserItem() { 41 } 42 43 ...続く 44} 45 46 47 48// TableView 49 50import UIKit 51 52class TableViewController: UIViewController { 53 54 @IBOutlet weak var tableView: UITableView! 55 var cDDataList: [CDData] = [] 56 57 override func viewDidLoad() { 58 self.cDDataList = [ 59 CDData(rank:1, title:"Turning up", artist:"嵐"), 60 CDData(rank:2, title:"馬と鹿", artist:"米津玄師"), 61 CDData(rank:3, title:"Pretender", artist:"official髭男dism") 62 ] 63 } 64 65 // めんどくさいので間、省略 66 67 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 68 let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as! CustomCell 69 cell.bind(data: self.cDDataList[indexPath.row]) 70 return cell 71 } 72 73}

投稿2019/12/03 04:26

hameji

総合スコア1380

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yoshiSwift

2019/12/03 12:39

見辛いコード、説明不足で申し訳ありませんでした。 hamejiさんが指摘して下さった通り、cellForRowAt 内で位置を調整したところ、上手くいきました。 ご回答ありがとうございました。 また、コードを直していたところ疑問が出てきましたので、質問させていただいてもよろしいでしょうか? 例えば、hamejiさんのコード:func bind2()を使用し、makeTag()でレイアウトを更新させたいとき、 makeTag() 内でlayoutIfNeede()を呼ぶという方法はいかがでしょうか?
hameji

2019/12/03 13:57 編集

できなくはないと思いますが、循環?している点、 システムで呼び出されるタイミングがあるので、 予期しない挙動などにつながると困るので、おすすめは出来ませんね。 どうしてもやりたいというなら、そもそも逆にmakeTag()内に書いて、 layoutIfNeeded()で逆にmakeTag()を呼び出せばいいのではないですか? ですが、layoutIfNeededは再利用では呼び出されないし、 書く意味がそもそもない気もします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問