ユーザーが投稿した投稿のフィードビューを持つアプリを作成しています。このビューには、カスタムUITableView
実装を持つUITableViewCell
があります。このセル内には、コメントを表示するための別のUITableView
があります。要旨は次のようなものです。
Feed TableView
PostCell
Comments (TableView)
CommentCell
PostCell
Comments (TableView)
CommentCell
CommentCell
CommentCell
CommentCell
CommentCell
最初のフィードはプレビュー用に3つのコメントとともにダウンロードされますが、さらにコメントがある場合、またはユーザーがコメントを追加または削除する場合は、フィードテーブルビュー内のPostCell
を追加または削除して更新したいCommentCells
内のコメントテーブルからPostCell
を削除します。私は現在、次のヘルパーを使用してそれを実現しています。
// (PostCell.Swift) Handle showing/hiding comments
func animateAddOrDeleteComments(startRow: Int, endRow: Int, operation: CellOperation) {
let table = self.superview?.superview as UITableView
// "table" is outer feed table
// self is the PostCell that is updating it's comments
// self.comments is UITableView for displaying comments inside of the PostCell
table.beginUpdates()
self.comments.beginUpdates()
// This function handles inserting/removing/reloading a range of comments
// so we build out an array of index paths for each row that needs updating
var indexPaths = [NSIndexPath]()
for var index = startRow; index <= endRow; index++ {
indexPaths.append(NSIndexPath(forRow: index, inSection: 0))
}
switch operation {
case .INSERT:
self.comments.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .DELETE:
self.comments.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .RELOAD:
self.comments.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
}
self.comments.endUpdates()
table.endUpdates()
// trigger a call to updateConstraints so that we can update the height constraint
// of the comments table to fit all of the comments
self.setNeedsUpdateConstraints()
}
override func updateConstraints() {
super.updateConstraints()
self.commentsHeight.constant = self.comments.sizeThatFits(UILayoutFittingCompressedSize).height
}
これにより、更新が正常に完了します。投稿は、期待どおりにPostCell
内で追加または削除されたコメントで適切に更新されます。フィードテーブルでPostCells
の自動サイズ設定を使用しています。 PostCell
のコメントテーブルはすべてのコメントを表示するように展開されますが、アニメーションは少しぎくしゃくしており、セルの更新アニメーションが行われている間、テーブルの並べ替えは数十ピクセル前後にスクロールします。
サイズ変更中のジャンプは少し面倒ですが、私の主な問題はその後に来ます。フィードを下にスクロールすると、スクロールは以前と同様にスムーズになりますが、コメントを追加した後にサイズを変更したばかりのセルの上にスクロールすると、フィードはフィードの上部に達する前に数回後方にジャンプします。セットアップiOS8
次のようなフィードのセルの自動サイズ調整:
// (FeedController.Swift)
// tableView is the feed table containing PostCells
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 560
estimatedRowHeight
を削除すると、セルの高さが変わるたびにテーブルが最上部にスクロールします。私は今これにかなり固執していると感じており、新しいiOS開発者として、あなたが持っているかもしれないあらゆるヒントを使用できます。
この種の問題(スクロール問題+ reloadRows + iOS 8 UITableViewAutomaticDimension)を解決するために私が見つけた最良のソリューションは次のとおりです。
TableViewがセルを表示するときに、すべての高さをディクショナリに保持し、(ディクショナリ内で)それらを更新することで構成されます。
次に、- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
メソッドで保存した高さを返します。
次のようなものを実装する必要があります。
Objective-C
- (void)viewDidLoad {
[super viewDidLoad];
self.heightAtIndexPath = [NSMutableDictionary new];
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath];
if(height) {
return height.floatValue;
} else {
return UITableViewAutomaticDimension;
}
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = @(cell.frame.size.height);
[self.heightAtIndexPath setObject:height forKey:indexPath];
}
Swift
@IBOutlet var tableView : UITableView?
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
同じ問題がありました。これは、SDKが不適切な高さを強制する原因となるセルの高さの不適切な推定に由来するものであり、スクロールバック時にセルがジャンプする原因となります。セルの構築方法に応じて、これを修正する最善の方法は、UITableViewDelegate
メソッド- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
を実装することです
推定値がセルの高さの実際の値にかなり近い限り、これはジャンプとジャーキネスをほぼキャンセルします。実装方法は次のとおりです。ロジックを取得できます。
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
// This method will get your cell identifier based on your data
NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];
if ([cellType isEqualToString:kFirstCellIdentifier])
return kFirstCellHeight;
else if ([cellType isEqualToString:kSecondCellIdentifier])
return kSecondCellHeight;
else if ([cellType isEqualToString:kThirdCellIdentifier])
return kThirdCellHeight;
else {
return UITableViewAutomaticDimension;
}
}
追加Swift 2サポート
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// This method will get your cell identifier based on your data
let cellType = reuseIdentifierForIndexPath(indexPath)
if cellType == kFirstCellIdentifier
return kFirstCellHeight
else if cellType == kSecondCellIdentifier
return kSecondCellHeight
else if cellType == kThirdCellIdentifier
return kThirdCellHeight
else
return UITableViewAutomaticDimension
}
dosdosの答えはSwift 2で私のために働いた
Ivarを宣言します
var heightAtIndexPath = NSMutableDictionary()
func viewDidLoad()で
func viewDidLoad() {
.... your code
self.tableView.rowHeight = UITableViewAutomaticDimension
}
次に、次の2つのメソッドを追加します。
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = self.heightAtIndexPath.objectForKey(indexPath)
if ((height) != nil) {
return CGFloat(height!.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let height = cell.frame.size.height
self.heightAtIndexPath.setObject(height, forKey: indexPath)
}
スウィフト3:
var heightAtIndexPath = [IndexPath: CGFloat]()
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return self.heightAtIndexPath[indexPath] ?? UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.heightAtIndexPath[indexPath] = cell.frame.size.height
}
私も同じ問題に直面していました。私は回避策を見つけましたが、それは完全にジャークを修正しません。しかし、以前の途切れたスクロールと比較して、はるかに優れているようです。
UITableView
デリゲートメソッド:cellForRowAtIndexPath:
、次の2つの方法を使用して、セルを返す前に制約を更新してみてください。 (スイフト言語)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
編集:また、tableView.estimatedRowHeight
値は、よりスムーズなスクロールを取得します。
@ dosdos に続きます。
実装するのも面白いと思いました:tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:
セルが既に画面に表示されている間に、セルが制約を動的に変更しているコード用に特に。このように辞書を更新すると、セルが2回表示されるときに役立ちます。
var heightAtIndexPath = [NSIndexPath : NSNumber]()
....
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = UITableViewAutomaticDimension
....
extension TableViewViewController: UITableViewDelegate {
//MARK: - UITableViewDelegate
func tableView(tableView: UITableView,
estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = heightAtIndexPath[indexPath]
if let height = height {
return CGFloat(height)
}
else {
return UITableViewAutomaticDimension
}
}
func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
func tableView(tableView: UITableView,
didEndDisplayingCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
}
@dosdosソリューションは正常に動作しています
ただし、追加する必要があるものがあります
@dosdosの回答をフォロー中
スイフト3/4
@IBOutlet var tableView : UITableView!
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
それからあなたが望むときはいつでもこの行を使用してください、私のために私は内部でそれを使用しますtextDidChange
最後にトップビューに移動します
tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(CGPoint.zero, animated: true)