4

Xcode 4.6.1 を使用しており、iOS 6.1 アプリを持っています。

別のテーブル ビューにドリルダウンするテーブル ビューがあります。2 番目のテーブル ビューは、カスタム セルを使用してデータを表示します。ただし、2 番目のテーブル ビューには問題があります。通常、そのコンテンツ サイズは正しくありません (具体的には、高すぎるか短すぎる高さ)。これにより、下部に余分なスペースが生じるか、下部までスクロールできる十分なスペースがありません。

下の図は、問題を再現するために作成したテスト アプリの 4 つのスクリーンショットを示しています。私の問題に至るまでの手順を順を追って説明します。

テスト アプリからの 4 つの画像

  1. アプリが読み込まれ、最初のテーブルが表示されます。2 番目のテーブルにドリルダウンする 3 つの行があります。

  2. 1 行目の「List: 0 to 10」を選択します。ナビゲーション コントローラーは、2 番目のテーブルを画面にプッシュします。11 行あり、(正しくは) 11 行分のコンテンツ サイズがあります。

  3. 次に戻って次の行、「リスト: A to P」を選択しました。2 番目のテーブルが再び画面に表示され、16 行になりました。ただし、11 行のコンテンツ サイズしかありません。残りの 5 行はまだ下にありますが、コンテンツのサイズが原因で 11 行目まで戻ってきます。

  4. 次に、戻って最後の行である「List: a to f」を選択しました。2 番目のテーブルが再び画面に表示され、6 行あります。ただし、コンテンツ サイズは 11 行のままです。テーブル ビューの下部にスペースがありすぎて、下部に何もない 5 つのセルが表示されます。

ここでのパターンは、コンテンツのサイズが最初にロードされたときのままであるということです。したがって、選択した最初の行は正しいコンテンツ サイズになりますが、他のすべての行は、行数に関係なく、そのサイズのままになります。これはまた、最初に 6 行ある "List: a to f" を選択した場合、他のすべてののコンテンツ サイズは 6 行になることを意味します。.

私のコードでは、コンテンツのサイズを自分で変更することは決してありません。これはすべて自動的に行われます。テーブル ビューのコンテンツ サイズが変更された時期と理由を把握するために、2 番目のテーブルの「contentSize」プロパティにオブザーバーを設定しました。これを行った後、コンテンツのサイズが同じままではないことに気付きましたが、行を選択したときに実際には2 回変更されていました。古いサイズ (テーブル ビューが表示されたとき)。

だから、なぜ元のサイズに戻ったのかを理解しようとしています。バックグラウンドで何が起こっているかを確認するために、コンテンツのサイズが変化するたびにスタック トレースを出力するようにしました。

以下に、これまでの手順のコンソール ログを示します。ただし、スペースを節約して読みやすくするために、長いスタック トレースを「(スタック トレース タイプ A)」のようなメッセージに置き換えました。詳細は後ほどご紹介しますが、とりあえずスタックトレースのタイプA、B、Cと呼んでいます。

first table: selected row 0 (List: 0 to 10)
second table: viewDidLoad
OBSERVED CHANGE: contentSize.height = 484 (row count = 11)
(Stack Trace Type A)
OBSERVED CHANGE: contentSize.height = 484 (row count = 11)
(Stack Trace Type B)
second table: viewWillAppear
OBSERVED CHANGE: contentSize.height = 484 (row count = 11)
(Stack Trace Type C)
second table: viewDidAppear
*going back to first table*
second table: viewWillDisappear
second table: viewDidDisappear

first table: selected row 1 (List: A to P)
OBSERVED CHANGE: contentSize.height = 704 (row count = 16)
(Stack Trace Type A)
second table: viewWillAppear
OBSERVED CHANGE: contentSize.height = 484 (row count = 16)
(Stack Trace Type C)
second table: viewDidAppear
*going back to first table*
second table: viewWillDisappear
second table: viewDidDisappear

first table: selected row 2 (List: a to f)
OBSERVED CHANGE: contentSize.height = 264 (row count = 6)
(Stack Trace Type A)
second table: viewWillAppear
OBSERVED CHANGE: contentSize.height = 484 (row count = 6)
(Stack Trace Type C)
second table: viewDidAppear
*going back to first table*
second table: viewWillDisappear
second table: viewDidDisappear

ご覧のとおり、コンテンツのサイズを元のサイズに戻しています。この場合、それは 484 になります。これは、高さ 44 の 11 個のセルを表示するのに十分なスペースです。正常に機能していれば、スタック トレース タイプ Cに関連する観察された変化は発生していません。つまり、タイプ C が問題を引き起こしているのです。

次に、スタック トレースが実際にどのように見えるかを示します。最初の 2 つ (A と B) は問題とは関係がないことに注意してください。問題を引き起こしていると思われるタイプ C と比較/対比できるように、それらを示しているだけです。

(スタック トレース タイプ A) テーブル ビューがリロードされてコンテンツ サイズが変更された場合、スタック トレースは次のようになりました。

(
    0   CustomTableCellTest   0x00002fe0 -[MasterViewController observeValueForKeyPath:ofObject:change:context:] + 320
    1   Foundation            0x00b0d417 NSKeyValueNotifyObserver + 357
    2   Foundation            0x00b26b24 NSKeyValueDidChange + 456
    3   Foundation            0x00adbd60 -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 131
    4   Foundation            0x00b48b80 _NSSetSizeValueAndNotify + 185
    5   UIKit                 0x000b57ef -[UITableView(_UITableViewPrivate) _updateContentSize] + 782
    6   UIKit                 0x000c3974 -[UITableView noteNumberOfRowsChanged] + 154
    7   UIKit                 0x000c32dc -[UITableView reloadData] + 769
    8   CustomTableCellTest   0x00004d97 -[MetaMasterViewController tableView:didSelectRowAtIndexPath:] + 567
    9   UIKit                 0x000c7285 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1194
    10  UIKit                 0x000c74ed -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 201
    11  Foundation            0x00ad15b3 __NSFireDelayedPerform + 380
    12  CoreFoundation        0x01c55376 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 22
    13  CoreFoundation        0x01c54e06 __CFRunLoopDoTimer + 534
    14  CoreFoundation        0x01c3ca82 __CFRunLoopRun + 1810
    15  CoreFoundation        0x01c3bf44 CFRunLoopRunSpecific + 276
    16  CoreFoundation        0x01c3be1b CFRunLoopRunInMode + 123
    17  GraphicsServices      0x01bf07e3 GSEventRunModal + 88
    18  GraphicsServices      0x01bf0668 GSEventRun + 104
    19  UIKit                 0x00017ffc UIApplicationMain + 1211
    20  CustomTableCellTest   0x000021ed main + 141
    21  CustomTableCellTest   0x00002115 start + 53
)

(スタック トレース タイプ B) これは、テーブル ビューが最初に表示されたときにのみ発生するため、一度だけ発生します。テーブルが最初にビューをロードしたときに発生すると思います。

(
    0   CustomTableCellTest   0x00002fe0 -[MasterViewController observeValueForKeyPath:ofObject:change:context:] + 320
    1   Foundation            0x00b0d417 NSKeyValueNotifyObserver + 357
    2   Foundation            0x00b26b24 NSKeyValueDidChange + 456
    3   Foundation            0x00adbd60 -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 131
    4   Foundation            0x00b48b80 _NSSetSizeValueAndNotify + 185
    5   UIKit                 0x000b57ef -[UITableView(_UITableViewPrivate) _updateContentSize] + 782
    6   UIKit                 0x000cbc8e -[UITableView _rectChangedWithNewSize:oldSize:] + 261
    7   UIKit                 0x000cc231 -[UITableView setFrame:] + 279
    8   UIKit                 0x000f5014 +[UIViewControllerWrapperView wrapperViewForView:frame:] + 448
    9   UIKit                 0x00110e18 -[UINavigationController _startTransition:fromViewController:toViewController:] + 239
    10  UIKit                 0x0011189b -[UINavigationController _startDeferredTransitionIfNeeded:] + 386
    11  UIKit                 0x00111e93 -[UINavigationController pushViewController:transition:forceImmediate:] + 1030
    12  UIKit                 0x00111a88 -[UINavigationController pushViewController:animated:] + 62
    13  CustomTableCellTest   0x00004e21 -[MetaMasterViewController tableView:didSelectRowAtIndexPath:] + 705
    14  UIKit                 0x000c7285 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1194
    15  UIKit                 0x000c74ed -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 201
    16  Foundation            0x00ad15b3 __NSFireDelayedPerform + 380
    17  CoreFoundation        0x01c55376 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 22
    18  CoreFoundation        0x01c54e06 __CFRunLoopDoTimer + 534
    19  CoreFoundation        0x01c3ca82 __CFRunLoopRun + 1810
    20  CoreFoundation        0x01c3bf44 CFRunLoopRunSpecific + 276
    21  CoreFoundation        0x01c3be1b CFRunLoopRunInMode + 123
    22  GraphicsServices      0x01bf07e3 GSEventRunModal + 88
    23  GraphicsServices      0x01bf0668 GSEventRun + 104
    24  UIKit                 0x00017ffc UIApplicationMain + 1211
    25  CustomTableCellTest   0x000021ed main + 141
    26  CustomTableCellTest   0x00002115 start + 53
)

したがって、タイプ A とタイプ B はどちらも、物事が正しく機能している場合に発生します。ただし、物事が正しく機能していない場合は、次のスタック トレースも取得します。

(スタック トレース タイプ C) コンテンツ サイズを元のサイズに戻したときに発生するスタック トレースです。

(
    0   CustomTableCellTest   0x00003080 -[MasterViewController observeValueForKeyPath:ofObject:change:context:] + 320
    1   Foundation            0x00b0d417 NSKeyValueNotifyObserver + 357
    2   Foundation            0x00b26b24 NSKeyValueDidChange + 456
    3   Foundation            0x00adbd60 -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 131
    4   Foundation            0x00b48b80 _NSSetSizeValueAndNotify + 185
    5   UIKit                 0x0007c213 -[UIScrollView _resizeWithOldSuperviewSize:] + 161
    6   UIKit                 0x0005cf2a -[UIView(Geometry) resizeWithOldSuperviewSize:] + 72
    7   UIKit                 0x0005bb28 __46-[UIView(Geometry) resizeSubviewsWithOldSize:]_block_invoke_0 + 80
    8   CoreFoundation        0x01cb85a7 __NSArrayChunkIterate + 359
    9   CoreFoundation        0x01c9003f __NSArrayEnumerate + 1023
    10  CoreFoundation        0x01c8fa16 -[NSArray enumerateObjectsWithOptions:usingBlock:] + 102
    11  UIKit                 0x0005babf -[UIView(Geometry) resizeSubviewsWithOldSize:] + 149
    12  UIKit                 0x00557dcc -[UIView(AdditionalLayoutSupport) _is_layout] + 143
    13  UIKit                 0x000607ae -[UIView(Hierarchy) layoutSubviews] + 80
    14  UIKit                 0x000682dd -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 279
    15  libobjc.A.dylib       0x010e76b0 -[NSObject performSelector:withObject:] + 70
    16  QuartzCore            0x02292fc0 -[CALayer layoutSublayers] + 240
    17  QuartzCore            0x0228733c _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 468
    18  QuartzCore            0x02287150 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 26
    19  QuartzCore            0x022050bc _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 324
    20  QuartzCore            0x02206227 _ZN2CA11Transaction6commitEv + 395
    21  QuartzCore            0x022068e2 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 96
    22  CoreFoundation        0x01c5eafe __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
    23  CoreFoundation        0x01c5ea3d __CFRunLoopDoObservers + 381
    24  CoreFoundation        0x01c3c7c2 __CFRunLoopRun + 1106
    25  CoreFoundation        0x01c3bf44 CFRunLoopRunSpecific + 276
    26  CoreFoundation        0x01c3be1b CFRunLoopRunInMode + 123
    27  GraphicsServices      0x01bf07e3 GSEventRunModal + 88
    28  GraphicsServices      0x01bf0668 GSEventRun + 104
    29  UIKit                 0x00017ffc UIApplicationMain + 1211
    30  CustomTableCellTest   0x000021ed main + 141
    31  CustomTableCellTest   0x00002115 start + 53
)

ご覧のとおり、タイプ A またはタイプ B には存在しなかったいくつかの QuartzCore が発生しています。また、タイプ C のいくつかの行では、実際には「OldSize」という単語を含むメソッドが舞台裏で呼び出されています。名前。どうやら、実際に「古いサイズ」に戻しているようです。しかし、なぜこれらの QuartzCore と "OldSize" メソッドがそもそも呼び出されているのか、私にはまだわかりません。

「resizeSubviewsWithOldSize」を調べてみましたが、そこにある唯一のドキュメントはNSViewに関するものでした(これは OS X アプリ用であり、UIView を使用する iOS アプリ用ではありません)。ドキュメントのこの説明は、この場合に呼び出された理由を理解するのにあまり役立ちませんでした。

ただし、なぜ呼び出されているのかはわかりませんが、いつ呼び出されているかについてはかなりよくわかります。さらにテストを重ねると、テーブル ビューが表示されるたびに古いコンテンツ サイズに戻ることがわかりました。

たとえば、モーダル ビューを画面全体に表示してから離すと、実際にはコンテンツ サイズが 3 回変更されました。ログメッセージは次のようになりました。

*Modal view entering*
second table: viewWillDisappear
OBSERVED CHANGE: contentSize.height = 484 (row count = 11)
(Stack Trace Type C)
second table: viewDidDisappear

*Modal view exiting*
second table: viewWillAppear
OBSERVED CHANGE: contentSize.height = 484 (row count = 11)
(Stack Trace Type C)
second table: viewDidAppear
OBSERVED CHANGE: contentSize.height = 484 (row count = 11)
(Stack Trace Type C)

したがって、変更はリロードされるテーブル ビューとは関係ありません。テーブル ビューの表示と非表示に関係しています。ビューが表示されるたびにサブビューをレイアウトしようとするので、カスタム セルのレイアウト方法に問題があるようです。

当初、問題はカスタム セルの設定方法 (コードまたは xib のいずれか) にあると考えていました。これは、カスタム セルを使用しないと問題が解消されたためです。しかし、カスタムセルxibで自動レイアウトをオフにすると、問題も解消されました。どうやら問題は自動レイアウトに関係しているようです。

デバッグを容易にするために、xib でセルを設定する方法は非常に単純です。セルには、背景色が灰色の UILabel しかありません。すべての自動レイアウト制約は、Interface Builder によって作成されたデフォルトのものです。カスタム セル xib の外観を以下に示します。

マスターセル xib

また、セルに何もないように UILabel を削除しようとしましたが、自動レイアウトをオンのままにしました。私がこれをしたとき、問題も解決しました。したがって、セルに UILabel を設定する方法に問題があると思います。削除できないInterface Builderによる「紫」のデフォルトの制約が設定されているため、これは奇妙です。

テーブル ビュー セルの自動レイアウトに関する情報を探し回った後、この質問に出会いました。どうやら、Interface Builder はその制約をセルのコンテンツ ビューに正しく設定していないようです。これが私の問題に関連しているかどうかはわかりませんでしたが、とにかく試しました。Adrian が投稿したこのようなソリューションを使用しました。

//  MasterCell.m
//  My custom UITableViewCell subclass

- (void)awakeFromNib {
    [super awakeFromNib];

    for (NSLayoutConstraint *cellConstraint in self.constraints) {

        [self removeConstraint:cellConstraint];

        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;

        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                     attribute:cellConstraint.firstAttribute
                                     relatedBy:cellConstraint.relation
                                        toItem:seccondItem
                                     attribute:cellConstraint.secondAttribute
                                    multiplier:cellConstraint.multiplier
                                      constant:cellConstraint.constant];

        [self.contentView addConstraint:contentViewConstraint];
    }
}

ただし、意図したことを実行しているように見えましたが (つまり、制約をセル自体ではなくコンテンツ ビューに関連するように変更しました)、これでも問題は解決しませんでした: コンテンツのサイズはまだ元に戻されていました古いサイズ。

結論として、このかなり長い質問を明確にするために、これは基本的に私が求めていることです。

カスタム テーブル セル xib で自動レイアウトを有効にすると、テーブルが画面に表示されるときに、テーブル ビューのコンテンツ サイズが誤って「古いサイズ」にサイズ変更されるのはなぜですか?

4

1 に答える 1

1

「マスター」テーブルのテーブル ビュー コントローラーの同じインスタンスを再利用していますが、間違った順序でメッセージを送信しています。

次のようにコントローラーをプッシュします。

if (!self.masterViewController) {
    self.masterViewController = [[MasterViewController alloc] init];
}

self.masterViewController.objects = self.masterObjects[indexPath.row];

[self.masterViewController.tableView reloadData];
[self.navigationController pushViewController:self.masterViewController animated:YES];

reloadData と push の行を入れ替えるだけで、問題は解決します。

ここで何が起こっているのか完全にはわかりませが、問題は、マスターコントローラーがポップされると、リロード時に適切なコンテンツサイズを計算するための参照フレーム (しゃれが意図されています!) がないことだと思います。であるため、この新しい高さのコンテンツ サイズがキャッシュされていない可能性があります。

次に、ビューがプッシュされると、スクロール ビューは突然再びコンテキストを持ち、再計算されたことを認識し、キャッシュされたコンテンツ サイズを使用します。とにかく、それは私が(あなたがしたように)スタックトレースから得た印象です。利用可能な古いサイズがないため、選択した最初のテーブルは常に問題ありません。これをストーリーボードで作成した場合、毎回新しいマスター コントローラーをインスタンス化することになり、問題に気付くことはありません。

autolayout が違いを生む理由については、ここでも推測して手を振っているだけですが、autolayout では、明示的なレイアウトよりもビュー コントローラーのライフサイクルのずっと後にすべてが発生します。autolayout がずっと下にある場合、スーパービューのないビュー (したがってサイズに制約がない) では、autolayout は必要なサイズを判断できません。おそらくバグですが、回避策は簡単で、私が言うように、通常、新しいビュー コントローラーをナビゲーション スタックにプッシュするときに、新しいビュー コントローラーを初期化し、ポップすると消えてしまうため、これ。

于 2013-03-24T13:04:22.693 に答える