Установка ограничений программно

Я экспериментирую с тем, как использовать UIScrollView. После долгих хлопот я, наконец, разобрался. Но теперь я, кажется, попал в другую загвоздку.

В этом простом приложении у меня есть представление прокрутки, и для того, чтобы оно работало, я должен установить нижнее пространство представления для ограничения прокрутки на 0, как описано здесь и все работает. Я делаю это через ИБ.

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

NSLayoutConstraint *bottomSpaceConstraint = [NSLayoutConstraint constraintWithItem:self.view
                                                                             attribute:NSLayoutAttributeBottom
                                                                             relatedBy:NSLayoutRelationEqual
                                                                                toItem:self.scrollView
                                                                             attribute:NSLayoutAttributeBottom
                                                                            multiplier:1.0
                                                                              constant:0.0];
[self.view addConstraint:bottomSpaceConstraint];

Но, похоже, это не работает. Он выводит следующее сообщение в окно консоли, и я не знаю, что с этим делать.

Невозможно одновременно удовлетворить ограничения. Вероятно, по крайней мере одно из ограничений в следующем списке вам не нужно. Попробуйте следующее: (1) посмотрите на каждое ограничение и попытайтесь выяснить, чего вы не ожидаете; (2) найти код, который добавил нежелательное ограничение или ограничения, и исправить его. (Примечание. Если вы видите NSAutoresizingMaskLayoutConstraints, которые не понимаете, обратитесь к документации по свойству UIView translatesAutoresizingMaskIntoConstraints) ( "", "" )

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

ОБНОВЛЕНИЕ:

Прежде всего спасибо за ответы. Используя способы, упомянутые в ответах, я смог заставить его работать. Однако в немного другом сценарии это не так. Теперь я пытаюсь загрузить представление на контроллер представления программно.

Если я могу объяснить дальше. Есть 2 контроллера представления. Первый - UITableViewController, второй - UIViewController. Внутри этого UIScrollView. Также есть несколько UIView, и высота некоторых из этих представлений превышает нормальную высоту экрана.

UITableViewController отображает список параметров. В зависимости от выбора пользователя конкретный UIView из лота будет загружен в UIViewController с UIScrollView.

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

Я загрузил демонстрационный проект здесь, чтобы вы могли увидеть его в действии. См. Email.xib и выберите Электронная почта в списке просмотра таблицы.


person Isuru    schedule 09.04.2013    source источник


Ответы (4)


Основываясь на обзоре вашего кода, несколько комментариев:

  1. Как правило, вам не нужно настраивать ограничения при появлении представления. Если вы обнаружите, что делаете это, это часто означает, что вы неправильно настроили свою раскадровку (или, по крайней мере, неэффективно). Единственный раз, когда вам действительно нужно установить/создать ограничения, это либо (а) вы добавляете представления программно (что, как я полагаю, требует больше работы, чем оно того стоит); или (b) вам нужно выполнить некоторую настройку ограничений во время выполнения (см. третий пункт в пункте 3 ниже).

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

    • Вы устанавливали рамку для просмотра прокрутки, но это регулируется ограничениями, поэтому ничего не происходит (т. е. когда ограничения применяются, любые установленные вручную настройки frame будут заменены). Как правило, при автоматической компоновке не пытайтесь изменить frame напрямую: отредактируйте ограничения. Но в любом случае изменение ограничений вообще не требуется, так что вопрос спорный.

    • Вы устанавливали размер содержимого для представления прокрутки, но в автоматическом макете это тоже регулируется ограничениями (подвидов), поэтому в этом не было необходимости.

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

  3. Я бы предложил более радикальное упрощение вашего проекта:

    • Вы усложняете себе жизнь, сохраняя свои взгляды в NIB. Это намного проще, если вы остаетесь в мире раскадровки. Мы можем помочь вам сделать NIB, если вам это действительно нужно, но зачем так усложнять себе жизнь?

    • Используйте прототипы ячеек, чтобы упростить проектирование ячеек в таблице. Вы также можете определить переходы от ячеек к следующей сцене. Это избавляет от необходимости писать код didSelectRowAtIndexPath или prepareForSegue. Понятно, что если у вас есть что-то, что вам нужно передать в следующую сцену, обязательно используйте prepareForSegue, но ничего из того, что вы представили до сих пор, не требует этого, поэтому я закомментировал это в своих примерах.

    • Предполагая, что вы искали практический пример программного изменения ограничений, я настроил сцену так, чтобы текстовое представление изменяло свою высоту программно на основе текста в текстовом представлении. Как всегда, вместо того, чтобы перебирать ограничения, чтобы найти нужное, при изменении существующего ограничения, созданного для меня IB, я думаю, что гораздо эффективнее установить IBOutlet для ограничения и отредактировать свойство constant для ограничения. напрямую, вот что я сделал. Поэтому я настроил контроллер представления в качестве делегата текстового представления и написал textViewDidChange, который обновил ограничение высоты текстового представления:

      #pragma mark - UITextViewDelegate
      
      - (void)textViewDidChange:(UITextView *)textView
      {
          self.textViewHeightConstraint.constant = textView.contentSize.height;
          [self.scrollView layoutIfNeeded];
      }
      

      Обратите внимание, что мое текстовое представление имеет два ограничения по высоте, обязательное минимальное ограничение по высоте и ограничение со средним приоритетом, которое я изменяю выше в зависимости от количества текста. Суть в том, что он иллюстрирует практический пример программного изменения ограничений. Вам вообще не нужно возиться с нижним ограничением прокрутки, но это показывает реальный пример того, когда вы можете захотеть настроить ограничение.


Когда вы добавляете прокрутку в IB, она автоматически получает все необходимые вам ограничения. Вы, вероятно, не хотите программно добавлять ограничение (по крайней мере, без удаления существующего нижнего ограничения).

Два подхода могут быть проще:

  1. Создайте IBOutlet для существующего нижнего ограничения, скажем, scrollViewBottomConstraint. Тогда вы можете просто сделать

    self.scrollViewBottomConstraint.constant = 0.0;
    
  2. Или создайте свое представление изначально в IB, где нижнее ограничение равно 0,0, и тогда вам вообще не нужно ничего делать программно. Если вы хотите отобразить длинный вид прокрутки и его подвиды, выберите контроллер, установите его смоделированные показатели с «выведенных» на «свободную форму». Затем вы можете изменить размер представления, установить верхние и нижние ограничения прокрутки равными нулю, разместить все, что вы хотите, внутри представления прокрутки, а затем, когда представление будет представлено во время выполнения, представление будет соответствующим образом изменено, и потому что вы определили верхние и нижние ограничения прокрутки равными 0,0, они будут изменены правильно. Это выглядит немного странно в IB, но работает как шарм, когда приложение работает.

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

Но вы определенно не хотите просто добавлять новое ограничение.

person Rob    schedule 09.04.2013
comment
Привет, Роб, спасибо за ответ. Я обновил существующее ограничение, и оно отлично работает. Однако теперь я пытаюсь загрузить представление из отдельного UIView, а затем оно не работает. Должен ли я делать что-то другое в этом случае? Я обновил свой вопрос. - person Isuru; 09.04.2013
comment
@Isuru Я просмотрел ваш проект и получил много комментариев, так что смотрите мой исправленный ответ. Кроме того, если вы загрузите мои образцы проектов, представляющие собой упрощенные версии вашей исходной кодовой базы, вы увидите дополнительные комментарии в исходном коде. - person Rob; 09.04.2013
comment
Привет, Роб, большое спасибо за все это. Это определенно один из лучших ответов, которые я получил на вопрос. Я прошел через оба проекта и многому научился. Я 4 месяца занимаюсь разработкой iOS, поэтому я новичок в некоторых вещах, но все хорошо. Причина, по которой я хотел использовать xib-файлы, заключается в том, что я работаю над этим приложением на работе, и пожилые люди настаивают на использовании одного UIViewController и имеют пользовательские интерфейсы типа почты и файлы nib templates, потому что их легче справиться, так как у нас уже есть много сцен, лежащих вокруг. - person Isuru; 10.04.2013
comment
Я загрузил демонстрационный проект здесь. Я очистю код в соответствии с тем, что вы показали мне в своих предыдущих примерах. Мне просто нужно сделать загрузочный файл nib, потому что он не отображается на данный момент. Ошибок тоже не выдает. Не могли бы вы сказать мне, что мне делать? Я знаю, что это требует слишком многого, но я был бы признателен, если бы я мог закончить эту часть. Спасибо. - person Isuru; 10.04.2013
comment
Я пытаюсь загрузить перо в методе initWithNibName UIViewController, но этот метод, похоже, не вызывается при загрузке контроллера представления. - person Isuru; 10.04.2013
comment
После дальнейших исследований я обнаружил, что мне следует использовать метод initWithCoder вместо initWithNibName, поэтому мне понравилось это, но теперь выдает ошибку (показана в комментариях под фрагментом кода). В качестве решения этой проблемы я наткнулся на этот ответ, но я потерялся в пункте №6. Я не вижу представления под розетками. - person Isuru; 10.04.2013
comment
@Isuru То, что вы используете раскадровки, не означает, что вы не можете использовать один контроллер представления. Просто используйте один и тот же контроллер представления для всех этих сцен, и вы можете указать подкласс представления для каждой, если хотите. Лично я не думаю, что архитектурно правильно иметь представление, взаимодействующее с моделью, поэтому у меня может быть один контроллер представления, который выполняет основную часть обработки, а затем подклассы для различных уникальных представлений. Куча способов борьбы. Но если они ищут код, которым легче управлять, отдельные NIB обычно не продвигают эту цель. - person Rob; 10.04.2013
comment
давайте продолжим обсуждение в чате - person Rob; 10.04.2013
comment
@Роб, спасибо за этот ответ! Я понятия не имел, что вы можете сделать IBOutlet для ограничения. Вы только что помогли мне решить серьезную проблему с макетом. - person David Schwartz; 24.12.2013
comment
Мне удалось решить мою проблему, изменив контроллер представления с предполагаемого размера на свободную форму, теперь у меня больше высоты в моем представлении, размещенном в прокрутке, спасибо - person Gustavo Baiocchi Costa; 14.11.2016

Можно создать выходы для представления ограничений макета в вашем контроллере представления. Просто выберите нужное ограничение в конструкторе интерфейса (например, с помощью «выбрать и изменить» на панели измерений представления, которое вы упорядочиваете). Затем перейдите на панель выходов и перетащите «Новый исходящий выход» в файл кода (.h или .m). Это привяжет ограничение к экземпляру NSLayoutConstraint, к которому вы можете получить доступ из своего контроллера и динамически настроить на лету (обычно через свойство constant, название которого плохое, поскольку оно вообще не является константой).

введите здесь описание изображения

(Обратите внимание, что в XCode 6 вы можете дважды щелкнуть ограничение, чтобы выбрать его для редактирования.)

введите здесь описание изображения

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

person devios1    schedule 01.04.2014

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

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

for(NSLayoutConstraint *constraint in self.view.constraints)
    {
        if(constraint.firstAttribute == NSLayoutAttributeBottom && constraint.secondAttribute == NSLayoutAttributeBottom &&
           constraint.firstItem == self.view && constraint.secondItem == self.scrollView)
        {
            constraint.constant = 0.0;
        }
    }

Надеюсь это поможет

Даже ответ Роба будет работать!

person Kunal    schedule 09.04.2013
comment
Привет, Кунал, еще раз спасибо за ответ. Я обновил существующее ограничение, используя ваш метод, и он отлично работает. Однако теперь я пытаюсь загрузить представление из отдельного UIView, а затем оно не работает. Должен ли я делать что-то другое в этом случае? Я обновил свой вопрос. - person Isuru; 09.04.2013
comment
Извините за задержку с ответом. В NewMailViewController следующий код вызывает проблему, если ([self.mailTypeName isEqualToString:@Email]) { NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@Email owner:self options:nil]; self.view = [nib objectAtIndex:0]; } Здесь вы переназначаете self.view представлению email.xib (а в mail.xib нет прокрутки). Надеюсь это поможет. - person Kunal; 09.04.2013
comment
Все в порядке. :) Я понимаю. Но я поставил UIScrollView в Email.xib, и это все равно не сработало. Что я должен делать? - person Isuru; 09.04.2013

Вы можете использовать https://github.com/SnapKit/Masonry для программного добавления ограничений.

Это сила AutoLayout NSLayoutConstraints с упрощенным, понятным и выразительным синтаксисом. Поддерживает iOS и OSX Auto Layout.

UIView *superview = self.view;

UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[superview addConstraints:@[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
                             attribute:NSLayoutAttributeTop
                             relatedBy:NSLayoutRelationEqual
                                toItem:superview
                             attribute:NSLayoutAttributeTop
                            multiplier:1.0
                              constant:padding.top],

[NSLayoutConstraint constraintWithItem:view1
                             attribute:NSLayoutAttributeLeft
                             relatedBy:NSLayoutRelationEqual
                                toItem:superview
                             attribute:NSLayoutAttributeLeft
                            multiplier:1.0
                              constant:padding.left],

[NSLayoutConstraint constraintWithItem:view1
                             attribute:NSLayoutAttributeBottom
                             relatedBy:NSLayoutRelationEqual
                                toItem:superview
                             attribute:NSLayoutAttributeBottom
                            multiplier:1.0
                              constant:-padding.bottom],

[NSLayoutConstraint constraintWithItem:view1
                             attribute:NSLayoutAttributeRight
                             relatedBy:NSLayoutRelationEqual
                                toItem:superview
                             attribute:NSLayoutAttributeRight
                            multiplier:1
                              constant:-padding.right],]];

буквально в несколько строк

Вот те же ограничения, созданные с помощью MASConstraintMaker.

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
    make.left.equalTo(superview.mas_left).with.offset(padding.left);
    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
    make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

Или даже короче

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(superview).with.insets(padding);
}];

делай все возможное ;)

person ERbittuu    schedule 18.08.2016
comment
TinyConstraints сейчас мой любимый: github.com/roberthein/TinyConstraints - person SushiHangover; 16.02.2017