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

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

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

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

Q&A

解決済

1回答

3006閲覧

TableViewのアコーディオンCustomHeaderのアニメーションの挙動がおかしい

kusukusu

総合スコア29

Swift

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

0グッド

1クリップ

投稿2020/05/19 15:15

編集2020/05/20 11:46

もう数日間ずっと調べても解決方法が見いだせなかったので質問させていただきます。

TableViewのSectionを開閉できるようにしているのですが、そのときのアニメーションがとてもキレイとは言えないおかしな挙動をしてしまいます。

無知なりに数日間ずっと解決法を調べて試してみましたが解決に至りませんでした。

どうかアドバイスを頂けないでしょうか?

ちなみにHeaderは複数のラベルなどを設置しているカスタムHeaderです。

必要と思われる部分のコードを抜き出しました。

Swift

1override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 2 3 return taskItems[section].isOpen ? taskItems[section].details.count : 0 4} 5 6// Sectionがタップされたときの処理 7@objc func sectionTapAction(_ sender: UIGestureRecognizer) { 8 if let section = sender.view?.tag { 9 10 taskItems[section].taskName = taskItems[section].details[0] 11 selectedSectionIndex = section 12 self.taskItems[self.selectedSectionIndex].isOpen = !self.taskItems[self.selectedSectionIndex].isOpen 13 self.tableView.reloadSections(IndexSet(integer: self.selectedSectionIndex) , with: .automatic) 14 15 } 16 }

追記

頂いた回答を踏まえて、TableView.reloadSectionsを
TableView.insertRowsおよびTableView.deleteRowsに変更してみました。

しかしまだおかしな挙動は改善されませんでした。

Swift

1// Sectionがタップされたときの処理 2@objc func sectionTapAction(_ sender: UIGestureRecognizer) { 3 if let section = sender.view?.tag { 4 5 taskItems[section].taskName = taskItems[section].details[0] 6 selectedSectionIndex = section 7 self.taskItems[self.selectedSectionIndex].isOpen = !self.taskItems[self.selectedSectionIndex].isOpen 8 9 var indexPaths = [IndexPath]() 10 for row in taskItems[section].details.indices { 11 let indexPath = IndexPath(row: row, section: section) 12 indexPaths.append(indexPath) 13 } 14 15 16 17 18 if taskItems[selectedSectionIndex].isOpen { 19 tableView.insertRows(at: indexPaths, with: .none) 20 tableView.endUpdates() 21 22 }else { 23 tableView.deleteRows(at: indexPaths, with: .none) 24 tableView.endUpdates() 25 } 26 } 27 }

追記

SectionのViewを返すviewForHeaderInSectionメソッドの中身を追加させていただきます。

Swift

1override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 2 let sectionView = UIView(frame:CGRect(x: 0,y: 0,width: 0,height: 0)) 3 4 5 6 let vividColors = ["FF7F3B","FF96D7","FF279F","BE00E8","5A2199","2D23D2","FF433B","3DB2FA"] 7 8 9 sectionView.backgroundColor = UIColor(hex: vividColors[Int.random(in: 0..<vividColors.count)]) 10 11 12 let dateLabel = UILabel(frame:CGRect(x: 50,y: 10,width: self.view.frame.width - 100 ,height: 20)) 13 dateLabel.font = UIFont.boldSystemFont(ofSize: 20) 14 dateLabel.textColor = .white 15 16 17 var repeatIconImageView: UIImageView = UIImageView() 18 if taskItems[section].details[2] != "なし" { 19 dateLabel.frame = CGRect(x: 70,y: 10,width: self.view.frame.width - 100 ,height: 20) 20 repeatIconImageView = UIImageView(frame: CGRect(x: 50,y: 13,width: 15 ,height: 15)) 21 repeatIconImageView.image = UIImage(named: "repeat") 22 }else { 23 repeatIconImageView = UIImageView(frame: CGRect(x: 50,y: 10,width: 0 ,height: 0)) 24 } 25 26 27 if taskItems[section].details[1] != "なし" { 28 let a = taskItems[section].details[1].replace("年", "/") 29 let b = a.replace("月", "/") 30 let c = b.replace("日", "") 31 32 let formatter = DateFormatter() 33 formatter.locale = Locale(identifier: "ja_JP") 34 formatter.dateFormat = "yyyy/MM/dd HH:mm" 35 let date = formatter.date(from: c) 36 let calendar = Calendar.current 37 38 39 let isSameDate = calendar.isDate(date!, inSameDayAs: Date()) 40 let isTomorrow = calendar.isDate(date!, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: calendar.startOfDay(for: Date()))!) 41 let isDayAfterTomorrow = calendar.isDate(date!, inSameDayAs: calendar.date(byAdding: .day, value: 2, to: calendar.startOfDay(for: Date()))!) 42 43 if isSameDate { 44 formatter.dateFormat = "今日 HH:mm" 45 dateLabel.text = formatter.string(from: date!) 46 }else if isTomorrow { 47 formatter.dateFormat = "明日 HH:mm" 48 dateLabel.text = formatter.string(from: date!) 49 }else if isDayAfterTomorrow { 50 formatter.dateFormat = "明後日 HH:mm" 51 dateLabel.text = formatter.string(from: date!) 52 }else { 53 dateLabel.text = taskItems[section].details[1] 54 } 55 56 if date! < Date() { 57 dateLabel.textColor = .red 58 sectionView.backgroundColor = .quaternaryLabel 59 60 dateLabel.text! += " 【超過!!】" 61 62 } 63 64 }else { 65 dateLabel.text = "日時設定なし" 66 } 67 68 69 sectionView.tag = section 70 var sectionTitle: UILabel! 71 sectionTitle = UILabel(frame:CGRect(x: 50,y: 40,width: self.view.frame.width - 100 ,height: 20)) 72 sectionTitle.font = UIFont.boldSystemFont(ofSize: 18) 73 sectionTitle.text = taskItems[section].taskName 74 sectionTitle.textColor = .white 75 sectionTitle.numberOfLines = 0 76 sectionTitle.sizeToFit() 77 78 // let tickBtn = UIButton(frame: CGRect(x: 10,y: sectionTitle.frame.maxY - 15,width: 30 ,height: 30)) 79 let tickBtn = UIButton(frame: CGRect(x: 10,y: 10,width: 30 ,height: 30)) 80 tickBtn.setImage(UIImage(named: taskItems[section].isComplete ? "interfaces" : "done"), for: .normal) 81 tickBtn.tag = section 82 tickBtn.addTarget(self, action: #selector(self.tickBtnAction(_:)), for: .touchDown) 83 84 let priorityLabel: UILabel = UILabel() 85 if taskItems[section].details[5] != "なし" { 86 priorityLabel.textColor = .systemBlue 87 priorityLabel.textAlignment = .center 88 priorityLabel.frame = CGRect(x: 50, y: 40, width: 15, height: 15) 89 sectionTitle.frame = CGRect(x: 70,y: 40,width: self.view.frame.width - 100 ,height: 20) 90 sectionTitle.sizeToFit() 91 92 93 switch taskItems[section].details[5] { 94 case "低": 95 priorityLabel.text = "!" 96 case "中": 97 priorityLabel.text = "!!" 98 case "高": 99 priorityLabel.text = "!!!" 100 default: break 101 } 102 priorityLabel.sizeToFit() 103 }else { 104 priorityLabel.frame = CGRect(x: 50, y: 50, width: 0, height: 0) 105 } 106 107 108 sectionView.layer.cornerRadius = 15 109 110 let memoLabel = UILabel() 111 if taskItems[section].details[3] != "" { 112 113 memoLabel.frame = CGRect(x: 50,y: (sectionTitle.frame.maxY + 10),width: self.view.frame.width - 100 ,height: 50) 114 memoLabel.numberOfLines = 0 115 memoLabel.font = UIFont.boldSystemFont(ofSize: 15) 116 memoLabel.textColor = .white 117 memoLabel.text = taskItems[section].details[3] 118 memoLabel.sizeToFit() 119 }else { 120 memoLabel.frame = CGRect(x: 50,y: sectionTitle.frame.maxY,width: 0 ,height: 0) 121 } 122 123 let urlBtn = UIButton(type: .system) 124 if taskItems[section].details[4] != "" { 125 urlBtn.frame = CGRect(x: 50,y: memoLabel.frame.maxY,width: self.view.frame.width - 100 ,height: 20) 126 urlBtn.tag = section 127 urlBtn.titleLabel!.font = UIFont.boldSystemFont(ofSize: 15) 128 urlBtn.contentHorizontalAlignment = .left 129 urlBtn.addTarget(self, action: #selector(self.urlBtnAction(_:)), for: .touchDown) 130 131 if let component: NSURLComponents = NSURLComponents(string: taskItems[section].details[4]) { 132 urlBtn.setTitle(component.host ?? taskItems[section].details[4], for: .normal) 133 } 134 urlBtn.sizeToFit() 135 }else { 136 urlBtn.frame = CGRect(x: 50,y: memoLabel.frame.maxY,width: 0 ,height: 10) 137 } 138 139 140 //セクションにタップジェスチャーを追加 141 sectionView.addGestureRecognizer(UITapGestureRecognizer(target: self,action:#selector(sectionTapAction(_:)))) 142 sectionView.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(sectionLongTapAction(_:)))) 143 sectionView.addSubview(tickBtn) 144 sectionView.addSubview(dateLabel) 145 sectionView.addSubview(sectionTitle) 146 sectionView.addSubview(memoLabel) 147 sectionView.addSubview(repeatIconImageView) 148 sectionView.addSubview(priorityLabel) 149 sectionView.addSubview(urlBtn) 150 151 152 153 urlBtn.bottomAnchor.constraint(equalTo: sectionView.bottomAnchor, constant: 0).isActive = true 154 155 156 return sectionView 157 }

Sectionをタップしたときにコンソールに表示されるメッセージ
不正な制約という内容のメッセージですが、もしかするとこれが関係ありそうな気がしています。

[LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. ~~~省略~~~

reloadSectionsの挙動

![

insertRowsおよびdeleteRowsの挙動

イメージ説明

スローモーションで動きを捉えてみました。
イメージ説明

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

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

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

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

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

hoshi-takanori

2020/05/19 15:29 編集

これってどういう作りになってますか? section header をクリックすると中身のセルが見える感じでしょうか?
kusukusu

2020/05/19 15:36

コードを追記させていただきました。 Sectionが開閉しているかどうかをBoolで保持し、 開いているならCellを表示。 閉じているならCellを非表示にしています。 更新にはTableView.reloadSectionsを使用しています。 よろしくおねがいします。
hoshi-takanori

2020/05/19 16:31

コードありがとうございます。ちょっと実験してみましたが、section の中身を空にするとアニメーションがおかしくなる場合がある気がしますね…。
TsukubaDepot

2020/05/19 21:32

よく全体が把握できていないので何ですが、たとえば下記URLと同じようなことを考えていらっしゃるのでしょうか。この例はきちんと表示されるようです。 https://daisa-n.com/blog/ios-swift-foldingtableview/ こちらの例は参考になりますでしょうか。
kusukusu

2020/05/20 11:13

ご回答ありがとうございます。 拝読しましたが、仕組み的にはこの記事と同じ内容のことをしております。 上の方にあるSectionは正常にキレイなアニメーションになるのですが、 スクロールして下の方にあるSectionをタップしたときにおかしな挙動になります。 そのときに制約がおかしいという旨のメッセージがコンソールに出力されます。 それが怪しそうなのですが未だ解決法がわからず...。 よろしくおねがいします。
guest

回答1

0

ベストアンサー

reload系は画面がちらつくのが宿命のような気がします。
それと折りたたむセクションが画面の下の方だと勝手にスクロールしてちょっと気持ち悪くなります。
insertRows/deleteRows系はアニメーションを生かしたまま終了検知後処理が辛かったような気がします。

https://github.com/tyobigoro/tFoldableTableView_copy

イメージ説明

投稿2020/05/19 15:59

編集2020/05/20 12:27
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

kusukusu

2020/05/19 16:50

ご回答ありがとうございます。 回答を踏まえてreloadSectionsから insertRowsおよびdeleteRowsに変更してみました。 しかしまだおかしな挙動は改善されません。 コードとgifを追記しました。 よろしくおねがいします
退会済みユーザー

退会済みユーザー

2020/05/22 02:58 編集

gifを見るとreloadSection/insertRowsとも位置がずれてるように見えますが、ロジック内でのインデックス算出に間違いはないでしょうか? とくにinsertRows&DeleteRowsの方はインデックスに間違いがなければズレは起こりませんので 個人的にはtagでセクションindexを取ると、セクション編集時に面倒くさくなるし間違いが起こりやすいく、 面倒くさいのを回避するためにtableViewをreloadするとクオリティが低くなると思います。
退会済みユーザー

退会済みユーザー

2020/05/20 08:28 編集

https://teratail.com/questions/149409 前にアニメーションが変だったので、きちんとheaderfooterviewを使い回して返すようにしたら、まともになったことがありました。今回の件に関係があるかはわかりません。 貼り付けたサンプルはそんな感じで作ってあったと思います。
kusukusu

2020/05/20 11:31

ご回答ありがとうございます。 UITableViewHeaderFooterViewクラスとxibを使用して、 view = tableView.dequeueReusableHeaderFooterView(withIdentifier: name)を試してみましたが 結果は変わらずでした。。。 最初にアプリを実行したときに表示されている部分、つまり上の方にあるSectionでは正常できれいなアニメーションが実行されるのですが、下にスクロールしないと見えない部分(下の方にあるSection)ではおかしな挙動になります。 その際に制約に問題があるという旨のメッセージがコンソールに出力されます。 それが原因なのではないかと考えているのですが未だ解決法が見つかっていません。 よろしくおねがいします。
退会済みユーザー

退会済みユーザー

2020/05/20 14:48 編集

変更するSectionのindexが想定通りのものがとれているか、 それとセル、セクションの高さの計算ができているかあたりも疑ってみてください。 しっかり作ってあれば、上の方でも下の方でも同じように折りたたみできます。 コンソールの警告は極力消す方向でお願いします。 サンプルちょっとなおして置いときました。 質問者さんのやつだとセクションとセルの間に空間がありましたので、footerで空間表現しときました。参考にでもしてください。
kusukusu

2020/05/20 14:27

ご回答ありがとうございます。 丁寧にコードまで載せてくださり本当に感謝しております。 コードを参考&改良しているときに Cellの高さをAutomaticDimensionで返すとアニメーションがおかしくなる事に気づきました。 これもSectionの数を10以上にして下の方のSectionを開閉したときにです。 現在開発中の自作のアプリはSectionがタップされたときにCellが表示され、 そのCellにはTextViewが入っています。 そのTextViewを入力された文字列によって可変にしているのでTableView.automaticDimensionを使用しています。 このままの機能を保ちつつキレイにアニメーションさせる方法はありませんでしょうか。 もう少しだけお付き合いいただけるとすごく助かります。 よろしくおねがいします。
退会済みユーザー

退会済みユーザー

2020/05/20 14:47 編集

私が貼っておいたサンプルは、sectionReloadの方は一箇所警告がでますが、まあ警告、不具合はないと言えますので、これにtextViewを乗せてみたらどうなるかご自身でサンプルいじってみて比べてみることをおすすめします。 私には質問者さんの作ったものを見ることができませんが、質問者さんは比較する2つのものを見て比べることができると思いますので。
kusukusu

2020/05/20 15:29

ありがとうございます。 その送ってくださったサンプルのSectionの数を増やし ViewDidLoadに tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 10000 heightForRowAtにautomaticDimensionを設定すると挙動がおかしくなりました。
退会済みユーザー

退会済みユーザー

2020/05/20 16:18 編集

上記3行のコードの記述場所を変更しました。 念の為、サンプル更新しておきます。 想定高さを使うならば、サンプルの方では考慮していないので設定を見直してください。
kusukusu

2020/05/21 15:47

ありがとうございます。 おかげさまで原因はestimatedRowHeightを記述すべき場所がViewDidLoadではなく、 heightForRowAtの中に書くべきだったという発見ができました。 結果アニメーションの挙動が安定し、問題が解決いたしました。 問題解決に至るまでの長い時間と労力をかけてくださったtyobigorouさんに心からの感謝を。 本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問