UITableView w UIScrollView przy użyciu autolayout

W tej chwili używam UITableView wraz z innymi poglądami, które są zawarte w UIScrollView. Chcę, aby UITableView miała taką samą wysokość jak jej wysokość zawartości.

Aby skomplikować sprawy, wstawiam / usuwam wiersze, aby uzyskać efekt akordeonu, aby gdy użytkownik dotknie wiersza, wyświetli więcej szczegółów dla tego wiersza.

Mam wstawianie / usuwanie zrobione, choć w tej chwili nie aktualizuje UIScrollView który jest jego superview tak aby Rozmiar zawartości UIScrollView jest przeliczany, a {[0] } wraz z innymi widokami w UIScrollView są wyświetlane poprawnie.

Jak mogę to zaimplementować, aby Rozmiar UIScrollView był dopasowany i prawidłowo ułożony, gdy zmieniam zawartość UITableView? Obecnie używam układu auto.

Author: Wez Sie Tato, 2013-06-27

5 answers

Po pierwsze, czy te inne widoki (rodzeństwo widoku tabeli) są ściśle powyżej i poniżej widoku tabeli? Jeśli tak, czy rozważałeś możliwość normalnego przewijania widoku tabeli i umieszczenie tych widoków zewnętrznych w widoku nagłówka i stopki widoku tabeli? Wtedy nie potrzebujesz widoku przewijania.

Po drugie, możesz przeczytać notatkę techniczną TN2154: UIScrollView i Autolayout Jeśli jeszcze tego nie zrobiłeś.

Po trzecie, biorąc pod uwagę informacje w tej notce technicznej, myślę, że kilka sposobów na to, co chcesz. Najczystszym jest prawdopodobnie stworzenie podklasy UITableView, która implementuje metodę intrinsicContentSize. Implementacja jest trywialna:

@implementation MyTableView

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

@end

Następnie pozwól układowi automatycznemu użyć wewnętrznego rozmiaru zawartości widoku tabeli. Utwórz ograniczenia między podglądami podrzędnymi widoku przewijania (w tym widoku tabeli), aby je ułożyć, i upewnij się, że istnieją ograniczenia dla wszystkich czterech krawędzi widoku przewijania.

Prawdopodobnie musisz wysłać invalidateIntrinsicContentSize do widoku tabeli w odpowiednie czasy (Podczas dodawania lub usuwania wierszy lub zmiany wysokości wierszy). Prawdopodobnie możesz po prostu zastąpić odpowiednie metody w MyTableView, aby to zrobić. Np. do [self invalidateIntrinsicContentSize] W -endUpdates, -reloadData, - insertRowsAtIndexPaths:withRowAnimation:, itd.

Oto wynik moich testów:

widok tabeli z wewnętrznym rozmiarem zawartości w widoku przewijania

Widok przewijania ma jasnoniebieskie tło. Czerwona górna etykieta i niebieska dolna etykieta są rodzeństwem widoku tabeli w widoku przewijania.

Oto Pełny kod źródłowy widoku kontroler w moim teście. Nie ma pliku 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
 101
Author: rob mayoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-27 06:17:19

Oprócz odpowiedzi Roba istnieje szybki przykład podklasy self-resizable z 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 lub 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)
    }

}

Użyłem go do umieszczenia widoku tabeli w innej komórce widoku tabeli z możliwością automatycznej zmiany rozmiaru.

 62
Author: MuHAOS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-11 11:45:19

Oto wersja obj-C. Jest oparty na rozwiązaniu od użytkownika @ 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
 12
Author: Klemen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-06 09:58:23

Kod@muhaos i @klemen-zagar bardzo mi pomógł, ale w rzeczywistości powoduje problem z wydajnością, uruchamiając niekończącą się pętlę układu, gdy Widok tableview jest zawarty w widoku stosu, który sam jest zawarty w widoku przewijania. Zobacz moje rozwiązanie poniżej.

@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
Author: mhoeller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-12 11:04:00

Możesz dodać Widok jako widok nagłówka i widok stopki tableview. ponieważ Widok tableview jest podglądem podrzędnym widoku przewijania. wykonaj poniższy przykład.

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;

A także możesz dodać Widok nagłówka i stopki tableview za pomocą prostego widoku przeciągnij i upuść do widoku tableview w storyboard i weź IBOutlet tego widoku.

 0
Author: KKRocks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-30 05:07:35