ストーリーボードのプロトタイプセルを使用してカスタムUITableViewCell
を構成しています。ただし、すべてのUILabel
s(および他のUI要素)はセルのcontentView
に追加されているようではなく、UITableViewCell
ビューに直接追加されているようです。コンテンツが自動的にシフト/インデントされないため(セルがcontentView
内にある場合はそうなる)、セルが編集モードになると、これにより問題が発生します。
Interface Builder/Storyboard/prototypeセルを使用してセルをレイアウトするときに、UI要素をcontentView
に追加する方法はありますか?私が見つけた唯一の方法は、コードですべてを作成し、[cell.contentView addSubView:labelOne]
セルをグラフィカルにレイアウトする方がはるかに簡単なので、これは素晴らしいことではありません。
さらに調査すると(セルのサブビュー階層を表示)、Interface BuilderはセルのcontentView
内にサブビューを配置しますが、見た目は異なります。
この問題の根本的な原因はiOS 6の自動レイアウトです。セルが編集モードになっている(インデントされている)と、contentView
もインデントされるため、contentView
内のすべてのサブビューがcontentView
内にあるために移動(インデント)するのは当然です。ただし、Interface Builderによって適用されるすべての自動レイアウト制約は、UITableViewCell
ではなく、contentView
自体に関連しているようです。これは、contentView
のインデントがあっても、その中に含まれているサブビューはそうではないことを意味します-制約がかかります。
たとえば、UILabel
をセルに配置して(セルの左側から10ポイントに配置すると)、IBは自動的に「Horizontal Space(10)」という制約を適用しました。ただし、この制約はUITableViewCell
ではなく、contentView
に関連しています。つまり、セルがインデントされ、contentView
が移動しても、ラベルは、UITableViewCell
の左側から10ポイントを維持するという制約に準拠しているため、ラベルはそのまま残ります。
残念ながら(私が知る限り)IBによって作成されたこれらの制約をIB自体から削除する方法はないので、ここで問題を解決しました。
セルのUITableViewCell
サブクラス内で、IBOutlet
という制約のcellLabelHSpaceConstraint
を作成しました。また、ラベル自体にIBOutlet
も必要です。これは、cellLabel
と呼んでいます。次に、-awakeFromNib
以下の方法:
- (void)awakeFromNib {
// -------------------------------------------------------------------
// We need to create our own constraint which is effective against the
// contentView, so the UI elements indent when the cell is put into
// editing mode
// -------------------------------------------------------------------
// Remove the IB added horizontal constraint, as that's effective
// against the cell not the contentView
[self removeConstraint:self.cellLabelHSpaceConstraint];
// Create a dictionary to represent the view being positioned
NSDictionary *labelViewDictionary = NSDictionaryOfVariableBindings(_cellLabel);
// Create the new constraint
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-10-[_cellLabel]" options:0 metrics:nil views:labelViewDictionary];
// Add the constraint against the contentView
[self.contentView addConstraints:constraints];
}
要約すると、上記はIBが自動的に追加した水平間隔制約を削除し(UITableViewCell
ではなくcontentView
に対して有効です)、独自の制約を定義してcontentView
に追加します。
私の場合、セル内の他のすべてのUILabels
は、cellLabel
の位置に基づいて配置されていたため、この要素の制約/位置を修正すると、他のすべてのものが適切に従い、正しく配置されました。ただし、より複雑なレイアウトを使用している場合は、他のサブビューでもこれを行う必要がある場合があります。
前述のように、XCodeのInterface BuilderはUITableViewCellのcontentViewを隠しています。実際には、UITableViewCellに追加されたすべてのUI要素は、実際にはcontentViewのサブビューです。
現時点では、IBはレイアウト制約に対して同じ魔法をかけていません。つまり、それらはすべてUITableViewCellレベルで表現されています。
回避策は、サブクラスのawakeFromNibにあり、すべてのNSAutoLayoutConstrainsをUITableViewCellからそのcontentViewに移動して、それらをcontentViewの観点から表現します。
-(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];
}
}
これがサブクラスです。他の回答のアイデアに基づいて、カスタムセルのベースを作成します。
@interface FixedTableViewCell ()
- (void)initFixedTableViewCell;
@end
@interface FixedTableViewCell : UITableViewCell
@end
@implementation FixedTableViewCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (nil != (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
[self initFixedTableViewCell];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self initFixedTableViewCell];
}
- (void)initFixedTableViewCell {
for (NSInteger i = self.constraints.count - 1; i >= 0; i--) {
NSLayoutConstraint *constraint = [self.constraints objectAtIndex:i];
id firstItem = constraint.firstItem;
id secondItem = constraint.secondItem;
BOOL shouldMoveToContentView = YES;
if ([firstItem isDescendantOfView:self.contentView]) {
if (NO == [secondItem isDescendantOfView:self.contentView]) {
secondItem = self.contentView;
}
}
else if ([secondItem isDescendantOfView:self.contentView]) {
if (NO == [firstItem isDescendantOfView:self.contentView]) {
firstItem = self.contentView;
}
}
else {
shouldMoveToContentView = NO;
}
if (shouldMoveToContentView) {
[self removeConstraint:constraint];
NSLayoutConstraint *contentViewConstraint = [NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
}
@end
サブクラス化の代替方法は、cellForRowAtIndexPathの制約を修正することです。
コンテナービュー内にセルのすべてのコンテンツを埋め込みます。次に、先頭と末尾の制約をテーブルビューセルではなくcell.contentViewにポイントします。
UIView *containerView = [cell viewWithTag:999];
UIView *contentView = [cell contentView];
//remove existing leading and trailing constraints
for(NSLayoutConstraint *c in [cell constraints]){
if(c.firstItem==containerView && (c.firstAttribute==NSLayoutAttributeLeading || c.firstAttribute==NSLayoutAttributeTrailing)){
[cell removeConstraint:c];
}
}
NSLayoutConstraint *trailing = [NSLayoutConstraint
constraintWithItem:containerView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeTrailing
multiplier:1
constant:0];
NSLayoutConstraint *leading = [NSLayoutConstraint
constraintWithItem:containerView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:contentView
attribute:NSLayoutAttributeLeading
multiplier:1
constant:0];
[cell addConstraint:trailing];
[cell addConstraint:leading];
これはiOS 7ベータ3で修正され、それ以降は回避策が不要になると思います(ただし、ほとんどの場合、空の操作になるため無害です)。
Skootaのコードに基づいています(私は初心者ですが、あなたが何をしたかはあまり知りませんが、すばらしい仕事です)私の提案は、すべてのものをEdge-to-Edgeコンテナービューに配置し、以下を追加することです:
セルのヘッダーファイルには、次のIBOutletsがあります。
@property (weak, nonatomic) IBOutlet UIView *container;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *leftConstrain;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightConstrain;
実装ファイルでは、awakeFromNibに以下を記述しています。
// Remove the IB added horizontal constraint, as that's effective gainst the cell not the contentView
[self removeConstraint:self.leftConstrain];
[self removeConstraint:self.rightConstrain];
// Create a dictionary to represent the view being positioned
NSDictionary *containerViewDictionary = NSDictionaryOfVariableBindings(_container);
// Create the new left constraint (0 spacing because of the Edge-to-Edge view 'container')
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-0-[_container]" options:0 metrics:nil views:containerViewDictionary];
// Add the left constraint against the contentView
[self.contentView addConstraints:constraints];
// Create the new constraint right (will fix the 'Delete' button as well)
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"[_container]-0-|" options:0 metrics:nil views:containerViewDictionary];
// Add the right constraint against the contentView
[self.contentView addConstraints:constraints];
繰り返しますが、上記はSkootaによって可能になりました。ありがとう!!!アルクレジットは彼に行きます。