[РЕДАКТИРОВАТЬ: Предупреждение: все последующее обсуждение, возможно, будет устаревшим или, по крайней мере, сильно смягченным iOS 8, которая больше не может ошибиться, вызывая компоновку во время применения преобразования представления.]
Autolayout vs. View Transforms
Autolayout не очень хорошо работает с трансформациями вида. Причина, насколько я могу заметить, заключается в том, что вы не должны связываться с фреймом представления, имеющего преобразование (отличное от преобразования идентификаторов по умолчанию) - но это именно то, что делает autolayout. Способ работы autolayout заключается в том, что во layoutSubviews
время выполнения преодолеваются все ограничения и соответственно устанавливаются кадры всех представлений.
Другими словами, ограничения не волшебство; они просто список дел. layoutSubviews
где список дел делается. И делает это, устанавливая рамки.
Я не могу не расценивать это как ошибку. Если я применю это преобразование к представлению:
v.transform = CGAffineTransformMakeScale(0.5,0.5);
Я ожидаю увидеть вид с центром в том же месте, что и раньше, и в половине размера. Но, в зависимости от его ограничений, это может быть совсем не то, что я вижу.
[На самом деле, здесь есть второй сюрприз: применение преобразования к представлению немедленно запускает компоновку. Это кажется мне еще одной ошибкой. Или, возможно, это сердце первой ошибки. То, что я ожидал бы, - это иметь возможность уйти с преобразованием по крайней мере до времени компоновки, например, устройство поворачивается - так же, как я могу сойти с анимацией кадра до времени компоновки. Но на самом деле время макета является немедленным, что кажется просто неправильным.]
Решение 1: нет ограничений
Одним из текущих решений является, если я собираюсь применить полупостоянное преобразование к представлению (а не просто как-то временно его покачивать), чтобы удалить все ограничения, влияющие на него. К сожалению, это, как правило, приводит к исчезновению вида с экрана, поскольку автоматическое расположение по-прежнему имеет место, и теперь нет никаких ограничений, указывающих нам, куда поместить представление. Поэтому, в дополнение к удалению ограничений, я установил в translatesAutoresizingMaskIntoConstraints
YES вид. Представление теперь работает по-старому, фактически не затрагиваемое автоматическим размещением. (Это будет влиять autolayout, очевидно, но неявная маска ограничение автоматического изменения вызывает его поведение , чтобы быть точно так же как это было раньше autolayout.)
Решение 2. Используйте только соответствующие ограничения
Если это кажется немного радикальным, другое решение состоит в том, чтобы установить ограничения для правильной работы с предполагаемым преобразованием. Например, если представление имеет размер только по его внутренней фиксированной ширине и высоте и расположено исключительно по его центру, преобразование масштаба будет работать так, как я ожидаю. В этом коде я удаляю существующие ограничения в subview ( otherView
) и заменяю их этими четырьмя ограничениями, придавая ему фиксированную ширину и высоту и закрепляя его чисто по центру. После этого мое масштабное преобразование работает:
NSMutableArray* cons = [NSMutableArray array];
for (NSLayoutConstraint* con in self.view.constraints)
if (con.firstItem == self.otherView || con.secondItem == self.otherView)
[cons addObject:con];
[self.view removeConstraints:cons];
[self.otherView removeConstraints:self.otherView.constraints];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterX relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:self.otherView.center.x]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:self.otherView.center.y]];
[self.otherView addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeWidth relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.width]];
[self.otherView addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.height]];
В результате, если у вас нет ограничений, которые влияют на фрейм представления, автоматическая разметка не коснется фрейма вида - это то, что вам нужно, когда задействовано преобразование.
Решение 3: Используйте Subview
Проблема с обоими вышеупомянутыми решениями состоит в том, что мы теряем преимущества ограничений для позиционирования нашей точки зрения. Итак, вот решение, которое решает это. Начните с невидимого представления, работа которого состоит исключительно в том, чтобы выступать в роли хоста, и используйте ограничения для его позиционирования. Внутри этого, поместите реальный взгляд как подпредставление. Используйте ограничения, чтобы расположить подпредставление в представлении хоста, но ограничьте эти ограничения ограничениями, которые не будут сопротивляться, когда мы применяем преобразование.
Вот иллюстрация:
Белый вид - это вид хоста; Вы должны притворяться, что оно прозрачное и, следовательно, невидимое. Красный вид - это его подпредставление, расположенное путем прикрепления его центра к центру основного вида. Теперь мы можем без проблем масштабировать и вращать красный вид вокруг его центра, и на самом деле иллюстрация показывает, что мы сделали это:
self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);
Между тем, ограничения на представление хоста сохраняют его в нужном месте, когда мы вращаем устройство.
Решение 4. Вместо этого используйте преобразования слоев
Вместо преобразований вида используйте преобразования слоя, которые не вызывают компоновку и, следовательно, не вызывают немедленного конфликта с ограничениями.
Например, эта простая «пульсирующая» анимация вида может полностью сломаться при автоматическом размещении:
[UIView animateWithDuration:0.3 delay:0
options:UIViewAnimationOptionAutoreverse
animations:^{
v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
v.transform = CGAffineTransformIdentity;
}];
Несмотря на то, что в конце не было никаких изменений в размере представления, просто установление его transform
расположения вызывает, и ограничения могут заставить представление прыгать вокруг. (Это похоже на ошибку или что?) Но если мы сделаем то же самое с Core Animation (используя CABasicAnimation и применив анимацию к слою представления), макет не произойдет, и он будет работать нормально:
CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.autoreverses = YES;
ba.duration = 0.3;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1)];
[v.layer addAnimation:ba forKey:nil];
layerView
узнать его ширину? Он придерживается своей правой стороны чему-то другому?