Программное создание интервала до ограничения ближайшего соседа

Часть экрана, который я создаю, включает в себя раздел с n представлениями. Я генерирую эти представления на лету в коде — это простые UIView подклассы.

Я использую ограничения AutoLayout для этого экрана и хочу, чтобы каждый вид автоматически позиционировал себя примерно на 15 пикселей ниже вида над ним.

В Xcode можно создать расстояние до ограничения ближайшего соседа , который, кажется, делает именно то, что я хочу.

Однако я не могу найти примеров, показывающих, как создать это в коде.

Можно ли программно создать ограничение «расстояние до ближайшего соседа»?


person Josh Earl    schedule 12.02.2014    source источник


Ответы (3)


Вы можете создать словарь ограничений и строку ограничений и применить их программно.

Добавление элементов и ключей в словарь тривиально, поэтому я не буду этим заниматься. Просто помните, что каждое представление в системе ограничений должно быть в словаре.

Создание строки формата — интересный момент. Предположительно, вы хотите добавить свои представления в качестве определенного представления, скажем, у него есть ключ NSString topView. Первая часть строки формата выглядит так

NSString *constraintBase = [NSString stringWithFormat:@"V:topView"];

Для каждого представления, которое вы хотите добавить, вы добавляете к этой строке

NSString *constraintString = [constraintBase stringByAppendingString:[NSString stringWithFormat:@"-15-%@", viewDictionaryKey]];

Наконец, примените, как обычно, ограничивающую строку, сконструированную строку формата визуального ограничения.

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:constraintString options:0 metrics:nil views:viewsToConstrain]];
person Adam Eberbach    schedule 12.02.2014

На самом деле я выполнял некоторые личные упражнения с автоматическим макетом и анимацией, когда наткнулся на ваш вопрос и решил расширить свою небольшую демонстрацию, которую вы можете скачать здесь.

Если я вас правильно понял, приведенный ниже фрагмент кода можно использовать для вдохновения. Небольшим усилием его можно было бы расширить и удалением видов, а также динамической высотой видов.

Обратите внимание, мой код включает решение с анимацией и без нее — последнее, конечно, проще.

#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
person Jens Schwarzer    schedule 13.02.2014
comment
Я бы не считал это ответом, если 99% кода не имеет ничего общего с вопросом. Просто вставка случайных фрагментов — это не ответ. - person Erik Aigner; 10.10.2016
comment
@ErikAigner, он попросил пример, который я сделал - не вижу, как это становится случайным фрагментом кода? Конечно, мой ответ мог включать в себя больше, чем он просил... Но мальчик, это какой-то старый код, поэтому и вопрос, и ответ стареют... Ура! - person Jens Schwarzer; 11.10.2016

Здесь представления, сгенерированные программно, добавляются к родительскому представлению. Когда представление создано, к нему должны быть добавлены его ограничения.

Но ограничения должны быть сопоставлены с предыдущим представлением, поэтому нам нужно идентифицировать предыдущее представление (последнее представление в родительском представлении).

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 созданного представления должно быть отключено, чтобы не возникало конфликта ограничений при изменении размера (созданного представления) в будущем.

person Mithun Ravindran    schedule 13.11.2014