Программно создает ограничение "расстояние до ближайшего соседа"
Часть экрана, которую я строю, включает в себя раздел с n
Просмотры. Я генерирую эти представления на лету в коде - они просты UIView
подклассы.
Я использую ограничения AutoLayout для этого экрана, и я хотел бы, чтобы каждое представление автоматически позиционировало себя примерно на 15 пикселей ниже представления над ним.
В XCode можно создать ограничение расстояния до ближайшего соседа, которое, кажется, делает именно то, что я хочу.
Тем не менее, я не могу найти какие-либо примеры, которые показывают, как создать это в коде.
Можно ли программно создать ограничение "расстояние до ближайшего соседа"?
3 ответа
На самом деле я выполнял некоторые личные упражнения по автоматическому макету и анимации, когда наткнулся на ваш вопрос и решил расширить мою небольшую демонстрацию, которую вы можете скачать здесь.
Если я правильно вас понял, фрагмент кода может быть использован для вдохновения. С небольшим усилием это может быть расширено за счет удаления видов, а также за счет динамической высоты видов.
Обратите внимание, что мой код включает решение с анимацией и без нее - последняя, конечно, более простая.
#import "Demo2ViewController.h"
@interface Demo2ViewController ()
{
NSMutableArray *_viewList;
NSDictionary *_metrics;
}
@end
@implementation Demo2ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[[self view] setBackgroundColor:[UIColor colorWithRed:.95 green:.95 blue:.95 alpha:1.0]];
_metrics = @{@"height": @30, // height of the views being added
@"space": @15}; // space between two views
// the first view
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectNull];
_viewList = [[NSMutableArray alloc] initWithObjects:textView, nil];
textView.text = [NSString stringWithFormat:@"view: %lu", (unsigned long)[_viewList count]];
// a button to add more views
UIButton *buttonAddView = [[UIButton alloc] initWithFrame:CGRectNull];
[buttonAddView setTitle:@"add new view" forState:UIControlStateNormal];
[buttonAddView setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[buttonAddView addTarget:self action:@selector(buttonPushed:) forControlEvents:UIControlEventTouchDown];
NSDictionary *subviews = NSDictionaryOfVariableBindings(textView, buttonAddView);
for (id view in [subviews allValues]) {
[[self view] addSubview:view];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
}
// initial constraints
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[textView]-|" options:0 metrics:nil views:subviews]];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[buttonAddView]-|" options:0 metrics:nil views:subviews]];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[textView(==height)]" options:0 metrics:_metrics views:subviews]];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[buttonAddView]-|" options:0 metrics:nil views:subviews]];
}
-(void)buttonPushed:(UIButton*)button
{
UITextView *prevView = [_viewList lastObject]; // get reference to previous view
// create a new view
UITextView *newView = [[UITextView alloc] initWithFrame:CGRectNull];
[[self view] addSubview:newView];
[newView setTranslatesAutoresizingMaskIntoConstraints:NO];
[_viewList addObject:newView];
newView.text = [NSString stringWithFormat:@"view: %lu", (unsigned long)[_viewList count]];
NSDictionary *subviews = NSDictionaryOfVariableBindings(prevView, newView);
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[newView]-|" options:0 metrics:nil views:subviews]];
#if 0
// without animation
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[prevView]-space-[newView(==height)]" options:0 metrics:_metrics views:subviews]];
[[self view] layoutIfNeeded];
#else
// with animation
// to begin with the new view gets zero height and no space to previous view
NSArray *tempConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[prevView][newView(==0)]" options:0 metrics:nil views:subviews];
[[self view] addConstraints:tempConstraints];
[[self view] layoutIfNeeded]; // to ensure zero height is the starting point for the animation
[newView setAlpha:0.0f]; // starting point for fade-in
[UIView animateWithDuration:0.25f animations:^{
[[self view] removeConstraints:tempConstraints]; // remove zero height constraint
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[prevView]-space-[newView(==height)]" options:0 metrics:_metrics views:subviews]]; // add final constraints
[newView setAlpha:1.0f]; // fade-in
[[self view] layoutIfNeeded];
}];
#endif
}
@end
Вы можете создать словарь ограничений и строку ограничений и применять их программно.
Добавление элементов и ключей в словарь тривиально, поэтому я не буду с этим сталкиваться. Просто помните, что каждое представление в системе ограничений должно быть в словаре.
Создание строки формата - интересный бит. Предположительно, вы хотите добавить свои представления в качестве определенного представления, скажем, оно имеет ключ NSString topView. Первая часть строки формата выглядит
NSString *constraintBase = [NSString stringWithFormat:@"V:topView"];
Для каждого представления, которое вы хотите добавить, вы добавляете к этой строке
NSString *constraintString = [constraintBase stringByAppendingString:[NSString stringWithFormat:@"-15-%@", viewDictionaryKey]];
Наконец, как обычно, используйте constraintString, созданную строку формата визуального ограничения.
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:constraintString options:0 metrics:nil views:viewsToConstrain]];
Здесь представления, которые генерируются программно, добавляются к родительскому представлению. Когда представление создано, его ограничения должны быть добавлены к нему.
Но ограничения должны быть сопоставлены с предыдущим представлением, и поэтому нам нужно идентифицировать предыдущее представление (последнее представление в родительском представлении)
NSArray *subViewList = [_vwParentView subviews];
UIView *lastView;
if (subViewList.count > 0) {
lastView = [subViewList lastObject];
}
Приведенный выше код поможет найти последний созданный вид. Когда представления создаются программно и добавляются в качестве вложенных представлений, представления будут добавляться в виде стека для parrentView, и, следовательно, созданное наконец представление будет последним объектом в массиве subViewList.
Примечание. Предполагается, что в качестве родительского просмотра назначено отдельное представление без подпредставлений.
UIView *contentView = [[UIView alloc]init];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:1.0 constant:initialWidth];
[contentView addConstraint:widthConstraint];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeHeight multiplier:1.0 constant:initialHeight];
[contentView addConstraint:heightConstraint];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
[_vwParentView addSubview:contentView];
NSLayoutConstraint *gapMaintainTopConstraint;
if (lastView == nil) {
gapMaintainTopConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:_vwParentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:15];
}
else
{
gapMaintainTopConstraint = [NSLayoutConstraint constraintWithItem:contentView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:lastView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:15];
}
[_vwParentView addConstraint:gapMaintainTopConstraint];
В случае изменения размера добавленного представления в будущем это должно быть достигнуто путем изменения его widthConstraint или heightConstraint, только тогда будет работать ограничение, которое связано с ним (для поддержания определенного промежутка). Размер не должен быть изменен с использованием кадров после этого. Значение - представления, основанные на ограничениях, должны обрабатываться только с использованием ограничений.
Ограничение "расстояние до ближайшего соседа" можно создать только после добавления созданного представления в качестве вспомогательного представления.
Свойство translatesAutoresizingMaskIntoConstraints созданного представления должно быть отключено, чтобы не возникало никакого конфликта ограничений при любых изменениях размера (созданного представления) в будущем.