IOS Autolayout с UIScrollview: почему представление содержимого прокрутки не заполняет прокрутку?
Следующий код (вызываемый в viewDidLoad) приводит к полностью красному экрану. Я ожидаю, что это будет полностью зеленый экран. Почему он красный? И как я могу сделать все зеленым?
UIScrollView* scrollView = [UIScrollView new];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrollView];
UIView* contentView = [UIView new];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
contentView.backgroundColor = [UIColor greenColor];
[scrollView addSubview:contentView];
NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView,contentView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:0 views:viewDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:0 views:viewDict]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewDict]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewDict]];
Ответы
Ответ 1
Ограничения с просмотром прокрутки работают несколько иначе, чем при других представлениях. Ограничения между contentView
и ее superview
(scrollView
) заключаются в scrollView
contentSize
, а не в его frame
. Это может показаться запутанным, но на самом деле это очень полезно, что означает, что вам никогда не придется настраивать contentSize
, но contentSize
будет автоматически настраиваться в соответствии с вашим контентом. Это поведение описано в Техническая нота TN2154.
Если вы хотите определить размер contentView
на экране или что-то в этом роде, вам нужно будет добавить ограничение между contentView
и основным видом, например. Это, по общему признанию, противоречиво для размещения контента в scrollview, поэтому я, вероятно, не советую этого, но это можно сделать.
Чтобы проиллюстрировать это понятие, размер contentView
будет определяться его содержимым, а не bounds
scrollView
, добавьте метку к вашему contentView
:
UIScrollView* scrollView = [UIScrollView new];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrollView];
UIView* contentView = [UIView new];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
contentView.backgroundColor = [UIColor greenColor];
[scrollView addSubview:contentView];
UILabel *randomLabel = [[UILabel alloc] init];
randomLabel.text = @"this is a test";
randomLabel.translatesAutoresizingMaskIntoConstraints = NO;
randomLabel.backgroundColor = [UIColor clearColor];
[contentView addSubview:randomLabel];
NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView, contentView, randomLabel);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:0 views:viewDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:0 views:viewDict]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewDict]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewDict]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[randomLabel]-|" options:0 metrics:0 views:viewDict]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[randomLabel]-|" options:0 metrics:0 views:viewDict]];
Теперь вы увидите, что contentView
(и, следовательно, contentSize
для scrollView
) настроены так, чтобы соответствовать метке со стандартными полями. И поскольку я не указывал ширину/высоту метки, которая будет корректироваться на основе текста, который вы помещаете в эту метку.
Если вы хотите, чтобы contentView
также отрегулировал ширину основного вида, вы можете переопределить свой viewDict
так же, а затем добавить эти дополнительные ограничения (в дополнение ко всем остальным выше):
UIView *mainView = self.view;
NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView, contentView, randomLabel, mainView);
[mainView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[contentView(==mainView)]" options:0 metrics:0 views:viewDict]];
Существует известная проблема (ошибка?) с многострочными метками в scrollviews, что если вы хотите, чтобы она изменялась в соответствии с количеством текста, вам нужно сделать некоторые ловкости руки, такие как:
dispatch_async(dispatch_get_main_queue(), ^{
randomLabel.preferredMaxLayoutWidth = self.view.bounds.size.width;
});
Ответ 2
Я попробовал ответить Робу с первого взгляда. НО!, если вы также включили масштабирование, этот код автозапуска будет мешать. Сохраняет изменение содержимого ContentView при масштабировании. То есть, если я увеличил масштаб (zoomScale > 1), я не смогу прокрутить детали за пределами экрана.
После нескольких дней борьбы с Autolayout, к сожалению, я не смог найти решения. В конце концов, это даже не имеет значения (wat? Ok, извините). В конце, , я просто удаляю Autolayout в contentView (используйте фиксированный размер contentView), а затем в layoutSubviews, я настраиваю self.scrollView.contentInset
. (Я использую Xcode5, iOS 7)
Я знаю, что это не является прямым решением этого вопроса. Но я просто хочу указать на простой способ обхода проблемы. Он отлично работает для центрирования содержимого contentView с фиксированным размером в scrollView с двумя строками кода! Надеюсь, это может помочь кому-то;)
- (void)layoutSubviews {
[super layoutSubviews];
// move contentView to center of scrollView by changing contentInset,
// because I CANNOT set this with AUTOLAYOUT!!!
CGFloat topInset = (self.frame.size.height - self.contentView.frame.size.height)/2;
self.scrollView.contentInset = UIEdgeInsetsMake(topInset, 0, 0, 0);
}
Ответ 3
В целом, Auto Layout рассматривает верхний, левый, нижний и правый края представления как видимые края. То есть, если вы привязываете представление к левому краю своего супервизора, вы действительно привязываете его к минимальному x-значению границ супервизора. Изменение начала координат супервизора не меняет положение представления.
Класс UIScrollView прокручивает его содержимое, изменяя начало его границ. Чтобы сделать эту работу с помощью автоматического макета, верхний, левый, нижний и правый края в представлении прокрутки теперь означают края его содержимого.
Ограничения для подзонов в представлении прокрутки должны приводить к заполнению размера, который затем интерпретируется как размер содержимого прокрутки. (Это не следует путать с методом intrinsicContentSize, используемым для автоматической компоновки.) Чтобы размер кадра представлений прокрутки с помощью Auto Layout, ограничения должны быть либо явными относительно ширины и высоты прокрутки, либо края прокрутки должны быть привязан к представлениям вне своего поддерева.
Обратите внимание, что вы можете заставить subview вид прокрутки отображаться как float (не прокручивать) по другому прокручиваемому контенту, создавая ограничения между представлением и представлением вне поддерева прокрутки, например, просмотр прокрутки.
Вот два примера настройки прокрутки, сначала смешанного подхода, а затем чистого подхода