web-dev-qa-db-ja.com

iOS 6 UITableViewCellプロトタイプセルでcontentViewがインデントされない

ストーリーボードのプロトタイプセルを使用してカスタムUITableViewCellを構成しています。ただし、すべてのUILabels(および他のUI要素)はセルのcontentViewに追加されているようではなく、UITableViewCellビューに直接追加されているようです。コンテンツが自動的にシフト/インデントされないため(セルがcontentView内にある場合はそうなる)、セルが編集モードになると、これにより問題が発生します。

Interface Builder/Storyboard/prototypeセルを使用してセルをレイアウトするときに、UI要素をcontentViewに追加する方法はありますか?私が見つけた唯一の方法は、コードですべてを作成し、[cell.contentView addSubView:labelOne]セルをグラフィカルにレイアウトする方がはるかに簡単なので、これは素晴らしいことではありません。

39
Skoota

さらに調査すると(セルのサブビュー階層を表示)、Interface BuilderはセルのcontentView内にサブビューを配置しますが、見た目は異なります。

この問題の根本的な原因はiOS 6の自動レイアウトです。セルが編集モードになっている(インデントされている)と、contentViewもインデントされるため、contentView内のすべてのサブビューがcontentView内にあるために移動(インデント)するのは当然です。ただし、Interface Builderによって適用されるすべての自動レイアウト制約は、UITableViewCellではなく、contentView自体に関連しているようです。これは、contentViewのインデントがあっても、その中に含まれているサブビューはそうではないことを意味します-制約がかかります。

たとえば、UILabelをセルに配置して(セルの左側から10ポイントに配置すると)、IBは自動的に「Horizo​​ntal 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の位置に基づいて配置されていたため、この要素の制約/位置を修正すると、他のすべてのものが適切に従い、正しく配置されました。ただし、より複雑なレイアウトを使用している場合は、他のサブビューでもこれを行う必要がある場合があります。

66
Skoota

前述のように、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];
  }
}
32
Adrian

これがサブクラスです。他の回答のアイデアに基づいて、カスタムセルのベースを作成します。

@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
9
Mihaylov

サブクラス化の代替方法は、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];
6
railwayparade

これはiOS 7ベータ3で修正され、それ以降は回避策が不要になると思います(ただし、ほとんどの場合、空の操作になるため無害です)。

2
Joseph Lord

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によって可能になりました。ありがとう!!!アルクレジットは彼に行きます。