これは一般的な問題であり、一般的な解決策があるかどうか疑問に思っていました。
基本的に、私のUITableViewにはすべてのセルの動的なセルの高さがあります。 UITableViewの上部にいないときにtableView.reloadData()
を押すと、上にスクロールするとびびります。
これは、スクロールしているときにデータをリロードしたため、UITableViewが表示される各セルの高さを再計算しているためだと思います。どのようにそれを緩和するのですか、または特定のIndexPathからUITableViewの最後までデータをリロードするだけですか?
さらに、一番上までスクロールできた場合、下にスクロールしてから上にスクロールできます。ジャンプすることは問題ありません。これはおそらく、UITableViewCellの高さがすでに計算されているためです。
ジャンプを防ぐには、セルが読み込まれるときにセルの高さを保存し、tableView:estimatedHeightForRowAtIndexPath
に正確な値を指定する必要があります。
// declare cellHeightsDictionary
NSMutableDictionary *cellHeightsDictionary;
// initialize in code (thanks to @Gerharbo)
cellHeightsDictionary = @{}.mutableCopy;
// declare table dynamic row height and create correct constraints in cells
tableView.rowHeight = UITableViewAutomaticDimension;
// save height
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
[cellHeightsDictionary setObject:@(cell.frame.size.height) forKey:indexPath];
}
// give exact height value
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = [cellHeightsDictionary objectForKey:indexPath];
if (height) return height.doubleValue;
return UITableViewAutomaticDimension;
}
受け入れられた答えのSwift 3バージョン。
var cellHeights: [IndexPath : CGFloat] = [:]
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? 70.0
}
ジャンプは、推定高さが悪いためです。 timateedRowHeightが実際の高さと異なるほど、テーブルが再ロードされたときにジャンプする可能性が高くなり、特にスクロールされた場合はさらに下になります。これは、テーブルの推定サイズが実際のサイズとは根本的に異なり、コンテンツのサイズとオフセットをテーブルに強制的に調整させるためです。そのため、推定される高さはランダムな値ではなく、高さが予想される値に近いはずです。あなたのセルが同じタイプの場合UITableViewAutomaticDimension
を設定したときにも経験しました
func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 100//close to your cell height
}
異なるセクションにさまざまなセルがある場合、より良い場所は
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//return different sizes for different cells if you need to
return 100
}
@ Igorこの場合、答えはうまく機能しています。Swift-4
コード。
// declaration & initialization
var cellHeightsDictionary: [IndexPath: CGFloat] = [:]
UITableViewDelegate
の以下のメソッドで
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// print("Cell height: \(cell.frame.size.height)")
self.cellHeightsDictionary[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = self.cellHeightsDictionary[indexPath] {
return height
}
return UITableViewAutomaticDimension
}
上記のすべての回避策を試しましたが、何も機能しませんでした。
数時間を費やし、考えられるすべてのフラストレーションを経験した後、これを修正する方法を見つけました。このソリューションは命の恩人です!魅力のように働いた!
Swift 4
let lastContentOffset = tableView.contentOffset
tableView.beginUpdates()
tableView.endUpdates()
tableView.layer.removeAllAnimations()
tableView.setContentOffset(lastContentOffset, animated: false)
コードをきれいに見せ、リロードするたびにこれらの行をすべて書かないようにするために、拡張機能として追加しました。
extension UITableView {
func reloadWithoutAnimation() {
let lastScrollOffset = contentOffset
beginUpdates()
endUpdates()
layer.removeAllAnimations()
setContentOffset(lastScrollOffset, animated: false)
}
}
最後に ..
tableView.reloadWithoutAnimation()
または実際にこれらの行をUITableViewCell
awakeFromNib()
メソッドに追加できます
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale
そして、通常のreloadData()
を行います
今日私はこれに遭遇し、観察しました:
cellForRowAtIndexPath
を上書きしても効果はありません。修正は実際には非常に簡単でした:
estimatedHeightForRowAtIndexPath
をオーバーライドし、正しい値が返されることを確認します。
これにより、UITableViewsでの奇妙なジッタリングやジャンプはすべて停止しました。
注:私は実際に私の細胞のサイズを知っています。可能な値は2つだけです。セルが本当に可変サイズの場合、cell.bounds.size.height
からtableView:willDisplayCell:forRowAtIndexPath:
をキャッシュすることをお勧めします
私はそれを修正するより多くの方法を使用します:
View Controllerの場合:
var cellHeights: [IndexPath : CGFloat] = [:]
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? 70.0
}
uITableViewの拡張として
func reloadSectionWithouAnimation(section: Int) {
UIView.performWithoutAnimation {
let offset = self.contentOffset
self.reloadSections(IndexSet(integer: section), with: .none)
self.contentOffset = offset
}
}
結果は
tableView.reloadSectionWithouAnimation(section: indexPath.section)
実際、reloadRowsAtIndexPaths
を使用して特定の行のみを再ロードできます。例:
tableView.reloadRowsAtIndexPaths(indexPathArray, withRowAnimation: UITableViewRowAnimation.None)
ただし、一般に、次のようにテーブルセルの高さの変化をアニメーション化することもできます。
tableView.beginUpdates()
tableView.endUpdates()
ここに少し短いバージョンがあります:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return self.cellHeightsDictionary[indexPath] ?? UITableViewAutomaticDimension
}
これはSwift4で私のために働いた:
extension UITableView {
func reloadWithoutAnimation() {
let lastScrollOffset = contentOffset
reloadData()
layoutIfNeeded()
setContentOffset(lastScrollOffset, animated: false)
}
}
ExpectedHeightForRowAtIndexPathメソッドを高い値(300fなど)でオーバーライドする
これで問題が解決するはずです:)
2つの異なるセルの高さがあります。
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160)
return Helper.makeDeviceSpecificCommonSize(cellHeight)
}
estimatedHeightForRowAtを追加した後、これ以上のジャンプはありませんでした。
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160)
return Helper.makeDeviceSpecificCommonSize(cellHeight)
}
IOS11で導入されたと思われるbugがあります。
reload
を実行すると、tableView contentOffSet
が予期せず変更されます。実際、contentOffset
はリロード後に変更されるべきではありません。 UITableViewAutomaticDimension
の計算ミスが原因で発生する傾向があります
リロードが完了したら、contentOffSet
を保存し、保存した値に戻す必要があります。
func reloadTableOnMain(with offset: CGPoint = CGPoint.zero){
DispatchQueue.main.async { [weak self] () in
self?.tableView.reloadData()
self?.tableView.layoutIfNeeded()
self?.tableView.contentOffset = offset
}
}
使い方は?
someFunctionThatMakesChangesToYourDatasource()
let offset = tableview.contentOffset
reloadTableOnMain(with: offset)
この答えは here から派生しました
ViewDidLoad()
で次を使用できます
tableView.estimatedRowHeight = 0 // if have just tableViewCells <br/>
// use this if you have tableview Header/footer <br/>
tableView.estimatedSectionFooterHeight = 0 <br/>
tableView.estimatedSectionHeaderHeight = 0
これらのソリューションはどれも私にとってはうまくいきませんでした。 Swift 4&Xcode 10.1 ...でしたこと.
ViewDidLoad()で、テーブルの動的な行の高さを宣言し、セルに正しい制約を作成します...
tableView.rowHeight = UITableView.automaticDimension
また、viewDidLoad()で、すべてのtableViewセルのペン先を次のようにtableviewに登録します。
tableView.register(UINib(nibName: "YourTableViewCell", bundle: nil), forCellReuseIdentifier: "YourTableViewCell")
tableView.register(UINib(nibName: "YourSecondTableViewCell", bundle: nil), forCellReuseIdentifier: "YourSecondTableViewCell")
tableView.register(UINib(nibName: "YourThirdTableViewCell", bundle: nil), forCellReuseIdentifier: "YourThirdTableViewCell")
TableView heightForRowAtで、indexPath.rowで各セルの高さと等しい高さを返します...
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
let cell = Bundle.main.loadNibNamed("YourTableViewCell", owner: self, options: nil)?.first as! YourTableViewCell
return cell.layer.frame.height
} else if indexPath.row == 1 {
let cell = Bundle.main.loadNibNamed("YourSecondTableViewCell", owner: self, options: nil)?.first as! YourSecondTableViewCell
return cell.layer.frame.height
} else {
let cell = Bundle.main.loadNibNamed("YourThirdTableViewCell", owner: self, options: nil)?.first as! YourThirdTableViewCell
return cell.layer.frame.height
}
}
次に、tableViewtimatedHeightForRowAtの各セルの推定行の高さを指定します。できる限り正確に...
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
return 400 // or whatever YourTableViewCell's height is
} else if indexPath.row == 1 {
return 231 // or whatever YourSecondTableViewCell's height is
} else {
return 216 // or whatever YourThirdTableViewCell's height is
}
}
それはうまくいくはずです...
TableView.reloadData()を呼び出すときにcontentOffsetを保存して設定する必要はありませんでした
cell.layoutSubviews()
のセルを返す前に、func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?
を呼び出してみてください。 iOS8の既知のバグです。