web-dev-qa-db-fra.com

UITableView dans UIScrollView à l'aide de la mise en page automatique

Pour le moment, j'utilise un UITableView avec d'autres vues contenues dans un UIScrollView. Je veux que le UITableView ait sa hauteur identique à la hauteur de son contenu.

Pour compliquer les choses, j'insère/supprime également des lignes pour fournir un effet d'accordéon afin que lorsque l'utilisateur appuie sur une ligne, il affiche plus de détails pour cette ligne.

J'ai fait l'insertion/la suppression, mais pour le moment, il ne met pas à jour l'UIScrollView qui est sa vue d'ensemble afin que la taille du contenu du UIScrollView soit recalculée et le UITableView avec les autres vues du UIScrollView s'affichent correctement.

Comment puis-je procéder pour l'implémenter afin que la taille de UIScrollView soit ajustée et son contenu correctement présenté lorsque je change le contenu de UITableView? J'utilise actuellement la mise en page automatique.

52
Matt Delves

Tout d'abord, ces autres vues (frères et sœurs de la vue de table) sont-elles strictement au-dessus et en dessous de la vue de table? Dans l'affirmative, avez-vous envisagé de laisser défiler la vue de table normalement et de placer ces vues extérieures dans les vues d'en-tête et de pied de page de la vue de table? Ensuite, vous n'avez pas besoin de la vue de défilement.

Deuxièmement, vous voudrez peut-être lire Note technique TN2154: UIScrollView et Autolayout si vous ne l'avez pas déjà fait.

Troisièmement, compte tenu des informations contenues dans cette note technique, je peux penser à quelques façons de faire ce que vous voulez. Le plus propre est probablement de créer une sous-classe de UITableView qui implémente la méthode intrinsicContentSize. L'implémentation est triviale:

@implementation MyTableView

- (CGSize)intrinsicContentSize {
    [self layoutIfNeeded]; // force my contentSize to be updated immediately
    return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height);
}

@end

Ensuite, laissez simplement la mise en page automatique utiliser la taille de contenu intrinsèque de la vue tableau. Créez les contraintes entre les sous-vues de la vue de défilement (y compris la vue de table) pour les disposer et assurez-vous qu'il existe des contraintes sur les quatre bords de la vue de défilement.

Vous devrez probablement envoyer invalidateIntrinsicContentSize à la vue de table aux moments appropriés (lorsque vous ajoutez ou supprimez des lignes ou modifiez la hauteur des lignes). Vous pouvez probablement remplacer les méthodes appropriées dans MyTableView pour ce faire. Par exemple. faire [self invalidateIntrinsicContentSize] dans -endUpdates, -reloadData, - insertRowsAtIndexPaths:withRowAnimation:, etc.

Voici le résultat de mes tests:

table view with intrinsic content size in scroll view

La vue de défilement a le fond bleu clair. L'étiquette supérieure rouge et l'étiquette inférieure bleue sont les frères et sœurs de la vue de table à l'intérieur de la vue de défilement.

Voici le code source complet du contrôleur de vue dans mon test. Il n'y a pas de fichier xib.

#import "ViewController.h"
#import "MyTableView.h"

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

@end

@implementation ViewController

- (void)loadView {
    UIView *view = [[UIView alloc] init];
    self.view = view;

    UIScrollView *scrollView = [[UIScrollView alloc] init];
    scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    scrollView.backgroundColor = [UIColor cyanColor];
    [view addSubview:scrollView];

    UILabel *topLabel = [[UILabel alloc] init];
    topLabel.translatesAutoresizingMaskIntoConstraints = NO;
    topLabel.text = @"Top Label";
    topLabel.backgroundColor = [UIColor redColor];
    [scrollView addSubview:topLabel];

    UILabel *bottomLabel = [[UILabel alloc] init];
    bottomLabel.translatesAutoresizingMaskIntoConstraints = NO;
    bottomLabel.text = @"Bottom Label";
    bottomLabel.backgroundColor = [UIColor blueColor];
    [scrollView addSubview:bottomLabel];

    UITableView *tableView = [[MyTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
    tableView.translatesAutoresizingMaskIntoConstraints = NO;
    tableView.dataSource = self;
    tableView.delegate = self;
    [scrollView addSubview:tableView];

    UILabel *footer = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)];
    footer.backgroundColor = [UIColor greenColor];
    footer.text = @"Footer";
    tableView.tableFooterView = footer;

    NSDictionary *views = NSDictionaryOfVariableBindings(
        scrollView, topLabel, bottomLabel, tableView);
    [view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"V:|[scrollView]|"
        options:0 metrics:nil views:views]];
    [view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"H:|[scrollView]|"
        options:0 metrics:nil views:views]];
    [view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"V:|[topLabel][tableView][bottomLabel]|"
        options:0 metrics:nil views:views]];
    [view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"H:|[topLabel]|"
        options:0 metrics:nil views:views]];
    [view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"H:|-8-[tableView]-8-|"
        options:0 metrics:nil views:views]];
    [view addConstraint:[NSLayoutConstraint
        constraintWithItem:tableView attribute:NSLayoutAttributeWidth
        relatedBy:NSLayoutRelationEqual
        toItem:view attribute:NSLayoutAttributeWidth
        multiplier:1 constant:-16]];
    [view addConstraints:[NSLayoutConstraint
        constraintsWithVisualFormat:@"H:|[bottomLabel]|"
        options:0 metrics:nil views:views]];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 20;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
    }
    cell.textLabel.text = [NSString stringWithFormat:@"Row %d", indexPath.row];
    return cell;
}

@end
92
rob mayoff

En plus de la réponse de rob, il y a Swift exemple de sous-classe auto-redimensionnable de UITableView:

Swift 2.x

class IntrinsicTableView: UITableView {

    override var contentSize:CGSize {
        didSet {
            self.invalidateIntrinsicContentSize()
        }
    }


    override func intrinsicContentSize() -> CGSize {
        self.layoutIfNeeded()
        return CGSizeMake(UIViewNoIntrinsicMetric, contentSize.height)
    }

}

Swift 3.x ou Swift 4.x

class IntrinsicTableView: UITableView {

    override var contentSize:CGSize {
        didSet {
            self.invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        self.layoutIfNeeded()
        return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
    }

}

Je l'ai utilisé pour mettre une vue de tableau dans une autre cellule de vue de tableau auto-redimensionnable.

53
MuHAOS

Voici la version obj-C. Il est basé sur une solution de l'utilisateur @MuHAOS

@implementation SizedTableView

- (void)setContentSize:(CGSize)contentSize {
  [super setContentSize:contentSize];
  [self invalidateIntrinsicContentSize];
}

- (CGSize)intrinsicContentSize {
  [self layoutIfNeeded]; // force my contentSize to be updated immediately
  return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height);
}


@end
10
Klemen

Le code de @ MuHAOS et de @ klemen-zagar m'a beaucoup aidé mais provoque en fait un problème de performances en déclenchant une boucle de mise en page sans fin lorsque la vue de table est contenue dans une vue de pile qui elle-même est contenue dans une vue de défilement. Voir ma solution ci-dessous.

@interface AutoSizingTableView ()
@property (nonatomic, assign) BOOL needsIntrinsicContentSizeUpdate;
@end

@implementation AutoSizingTableView

- (void)setContentSize:(CGSize)contentSize
{
    [super setContentSize:contentSize];

    self.needsIntrinsicContentSizeUpdate = YES;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (!self.needsIntrinsicContentSizeUpdate) {
            return;
        }

        self.needsIntrinsicContentSizeUpdate = NO;
        [self layoutIfNeeded];
        [self invalidateIntrinsicContentSize];
    });
}

- (CGSize)intrinsicContentSize
{
    return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height);
}

@end
2
mhoeller

vous pouvez ajouter une vue en tête et en pied de table. car la vue de table est la sous-vue de scrollview. suivez l'exemple ci-dessous.

UILabel *topLabel = [[UILabel alloc] init];
topLabel.translatesAutoresizingMaskIntoConstraints = NO;
topLabel.text = @"Top Label";
topLabel.backgroundColor = [UIColor redColor];
tableView.tableFooterView = topLabel;

UILabel *footer = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)];
     footer.backgroundColor = [UIColor greenColor];
     footer.text = @"Footer";
     tableView.tableFooterView = footer;

et vous pouvez également ajouter la vue de tête et la vue de pied de la vue de table en utilisant une simple vue glisser-déposer à la vue de table dans le storyboard et prendre IBOutlet de ces vues.

0
KKRocks