Руководство по планированию верхнего уровня навигационного контроллера не выполнено с помощью пользовательского перехода
Краткая версия:
У меня возникла проблема с руководством по компоновке верхнего макета при использовании в сочетании с пользовательским переходом и UINavigationController
в iOS7. В частности, ограничение между верхним макетом и текстом не выполняется. Кто-нибудь столкнулся с этой проблемой?
Длинная версия:
У меня есть сцена, которая однозначно определяет ограничения (т.е. верхний, нижний, левый и правый), который отображает вид следующим образом:
![right]()
Но когда я использую это с настраиваемым переходом на контроллере навигации, верхнее ограничение в верхнем руководстве по расположению кажется выключенным, и оно отображается следующим образом, как если бы верхний макет был наверху экрана, а не в нижней части навигационного контроллера:
![wrong]()
Похоже, что "руководство по верстке" с контроллером навигации путается при использовании настраиваемого перехода. Остальные ограничения применяются правильно. И если я поверну устройство и поверну его снова, все будет внезапно отображаться правильно, поэтому не кажется, что ограничения не определены должным образом. Аналогично, когда я выключаю свой пользовательский переход, представления отображаются правильно.
Сказав, что _autolayoutTrace
сообщает, что объекты UILayoutGuide
страдают от AMBIGUOUS LAYOUT
, когда я запускаю:
(lldb) po [[UIWindow keyWindow] _autolayoutTrace]
Но эти руководства по макетам всегда сообщаются как неоднозначные, когда я смотрю на них, хотя я гарантировал отсутствие отсутствующих ограничений (я сделал обычный выбор контроллера вида и выбрав "Добавить недостающие ограничения для контроллера вида", или выбрать все элементы управления и сделать то же самое для них).
В терминах того, насколько точно я делаю переход, я указал объект, который соответствует UIViewControllerAnimatedTransitioning
в методе animationControllerForOperation
:
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController*)fromVC
toViewController:(UIViewController*)toVC
{
if (operation == UINavigationControllerOperationPush)
return [[PushAnimator alloc] init];
return nil;
}
и
@implementation PushAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
CGFloat width = fromViewController.view.frame.size.width;
toViewController.view.transform = CGAffineTransformMakeTranslation(width, 0);
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.transform = CGAffineTransformMakeTranslation(-width / 2.0, 0);
toViewController.view.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
@end
Я также выполнил описанное выше, установив frame
представления, а не transform
, с тем же результатом.
Я также попытался вручную убедиться, что ограничения повторно применяются, вызвав layoutIfNeeded
. Я также пробовал setNeedsUpdateConstraints
, setNeedsLayout
и т.д.
В нижней строке, кто-нибудь успешно женился на пользовательском переходе контроллера навигации с ограничениями, использующими руководство по началу размещения?
Ответы
Ответ 1
Я решил это, установив ограничение на высоту topLayoutGuide
. Настройка edgesForExtendedLayout
не была для меня невозможной, так как мне было необходимо, чтобы в режиме просмотра была скрыта панель навигации, а также чтобы можно было подобрать макеты с помощью topLayoutGuide
.
Непосредственная проверка ограничений в игре показывает, что iOS добавляет ограничение высоты к topLayoutGuide
со значением, равным высоте навигационной панели навигационного контроллера. Кроме того, в iOS 7 использование настраиваемого перехода анимации оставляет ограничение с высотой 0. Они исправили это в iOS 8.
Это решение, которое я придумал для исправления ограничения (это в Swift, но эквивалент должен работать в Obj-C). Я тестировал, что он работает на iOS 7 и 8.
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view
let destinationVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
destinationVC.view.frame = transitionContext.finalFrameForViewController(destinationVC)
let container = transitionContext.containerView()
container.addSubview(destinationVC.view)
// Custom transitions break topLayoutGuide in iOS 7, fix its constraint
if let navController = destinationVC.navigationController {
for constraint in destinationVC.view.constraints() as [NSLayoutConstraint] {
if constraint.firstItem === destinationVC.topLayoutGuide
&& constraint.firstAttribute == .Height
&& constraint.secondItem == nil
&& constraint.constant == 0 {
constraint.constant = navController.navigationBar.frame.height
}
}
}
// Perform your transition animation here ...
}
Ответ 2
Удалось устранить проблему, добавив эту строку:
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
To:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC fromView:(UIView *)fromView toView:(UIView *)toView {
// Add the toView to the container
UIView* containerView = [transitionContext containerView];
[containerView addSubview:toView];
[containerView sendSubviewToBack:toView];
// animate
toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration animations:^{
fromView.alpha = 0.0;
} completion:^(BOOL finished) {
if ([transitionContext transitionWasCancelled]) {
fromView.alpha = 1.0;
} else {
// reset from- view to its original state
[fromView removeFromSuperview];
fromView.alpha = 1.0;
}
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
Из документации Apple для [finalFrameForViewController]: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewControllerContextTransitioning_protocol/#//apple_ref/occ/intfm/UIViewControllerContextTransitioning/finalFrameForViewController:
Ответ 3
Я боролся с той же проблемой. Помещение этого в viewDidLoad моего toViewController действительно помогло мне:
self.edgesForExtendedLayout = UIRectEdgeNone;
Это не решило все мои проблемы, и я все еще ищу лучший подход, но это, конечно, сделало это немного легче.
Ответ 4
FYI, я в конечном итоге использовал вариант ответа Alex, программно изменяя константу ограничения высоты верхней вершины макета в методе animateTransition
. Я просто размещаю это, чтобы поделиться версией Objective-C (и устранить тест constant == 0
).
CGFloat navigationBarHeight = toViewController.navigationController.navigationBar.frame.size.height;
for (NSLayoutConstraint *constraint in toViewController.view.constraints) {
if (constraint.firstItem == toViewController.topLayoutGuide
&& constraint.firstAttribute == NSLayoutAttributeHeight
&& constraint.secondItem == nil
&& constraint.constant < navigationBarHeight) {
constraint.constant += navigationBarHeight;
}
}
Спасибо, Алекс.
Ответ 5
Просто поставьте следующий код в viewDidLoad
self.extendedLayoutIncludesOpaqueBars = YES;
Ответ 6
Как упоминалось в @Rob, topLayoutGuide
не является надежным при использовании пользовательских переходов в UINavigationController
. Я работал над этим, используя свой собственный макет. Вы можете увидеть код в действии в демонстрационном проекте . Основные характеристики:
Категория для настраиваемых руководств по макетам:
@implementation UIViewController (hp_layoutGuideFix)
- (BOOL)hp_usesTopLayoutGuideInConstraints
{
return NO;
}
- (id<UILayoutSupport>)hp_topLayoutGuide
{
id<UILayoutSupport> object = objc_getAssociatedObject(self, @selector(hp_topLayoutGuide));
return object ? : self.topLayoutGuide;
}
- (void)setHp_topLayoutGuide:(id<UILayoutSupport>)hp_topLayoutGuide
{
HPLayoutSupport *object = objc_getAssociatedObject(self, @selector(hp_topLayoutGuide));
if (object != nil && self.hp_usesTopLayoutGuideInConstraints)
{
[object removeFromSuperview];
}
HPLayoutSupport *layoutGuide = [[HPLayoutSupport alloc] initWithLength:hp_topLayoutGuide.length];
if (self.hp_usesTopLayoutGuideInConstraints)
{
[self.view addSubview:layoutGuide];
}
objc_setAssociatedObject(self, @selector(hp_topLayoutGuide), layoutGuide, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
HPLayoutSupport
- это класс, который будет выступать в качестве руководства по компоновке. Он должен быть подклассом UIView
, чтобы избежать сбоев (интересно, почему это не является частью интерфейса UILayoutSupport
).
@implementation HPLayoutSupport {
CGFloat _length;
}
- (id)initWithLength:(CGFloat)length
{
self = [super init];
if (self)
{
self.translatesAutoresizingMaskIntoConstraints = NO;
self.userInteractionEnabled = NO;
_length = length;
}
return self;
}
- (CGSize)intrinsicContentSize
{
return CGSizeMake(1, _length);
}
- (CGFloat)length
{
return _length;
}
@end
UINavigationControllerDelegate
является ответственным за "исправление" руководства по компоновке перед переходом:
- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
toVC.hp_topLayoutGuide = fromVC.hp_topLayoutGuide;
id <UIViewControllerAnimatedTransitioning> animator;
// Initialise animator
return animator;
}
Наконец, UIViewController
использует hp_topLayoutGuide
вместо topLayoutGuide
в ограничениях и указывает это, переопределяя hp_usesTopLayoutGuideInConstraints
:
- (void)updateViewConstraints
{
[super updateViewConstraints];
id<UILayoutSupport> topLayoutGuide = self.hp_topLayoutGuide;
// Example constraint
NSDictionary *views = NSDictionaryOfVariableBindings(_imageView, _dateLabel, topLayoutGuide);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[topLayoutGuide][_imageView(240)]-8-[_dateLabel]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views];
[self.view addConstraints:constraints];
}
- (BOOL)hp_usesTopLayoutGuideInConstraints
{
return YES;
}
Надеюсь, что это поможет.
Ответ 7
я нашел способ. Сначала снимите флажок "Extend Edges" свойства контроллера. после этого навигационная панель становится темным цветом. Добавьте представление в контроллер и установите верхний и нижний LayoutConstraint -100. Затем создайте свойство viewviewviewview no (для транскулентного эффекта navigaionbar). Мой английский плохой сори для этого.:)
Ответ 8
У меня была та же проблема, в результате я создал собственное руководство по topLayout и сделал для него ограничения, а не topLayoutGuide. Не идеально. Публикуйте его здесь только в том случае, если кто-то застрял и ищет быстрое хакерское решение http://www.github.com/stringcode86/SCTopLayoutGuide
Ответ 9
попробуйте:
self.edgesforextendedlayout=UIRectEdgeNone
Или просто настройте навигационную панель непрозрачной и установите фоновое изображение или backgroundcolor на панель навигации
Ответ 10
Здесь простое решение, которое я использую, отлично работает для меня: во время фазы настройки - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
вручную установите "from" и "to" viewController.view.frame.origin.y = navigationController.navigationBar.frame.size.height. Это сделает ваши представления макета автоматически позиционируются вертикально, как вы ожидаете.
Минус псевдокода (например, у вас, вероятно, есть собственный способ определения, работает ли устройство iOS7), вот как выглядит мой метод:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
CGAffineTransform destinationTransform;
UIViewController *targetVC;
CGFloat adjustmentForIOS7AutoLayoutBug = 0.0f;
// We're doing a view controller POP
if(self.isViewControllerPop)
{
targetVC = fromViewController;
[container insertSubview:toViewController.view belowSubview:fromViewController.view];
// Only need this auto layout hack in iOS7; it fixed in iOS8
if(_device_is_running_iOS7_)
{
adjustmentForIOS7AutoLayoutBug = toViewController.navigationController.navigationBar.frame.size.height;
[toViewController.view setFrameOriginY:adjustmentForIOS7AutoLayoutBug];
}
destinationTransform = CGAffineTransformMakeTranslation(fromViewController.view.bounds.size.width,adjustmentForIOS7AutoLayoutBug);
}
// We're doing a view controller PUSH
else
{
targetVC = toViewController;
[container addSubview:toViewController.view];
// Only need this auto layout hack in iOS7; it fixed in iOS8
if(_device_is_running_iOS7_)
{
adjustmentForIOS7AutoLayoutBug = toViewController.navigationController.navigationBar.frame.size.height;
}
toViewController.view.transform = CGAffineTransformMakeTranslation(toViewController.view.bounds.size.width,adjustmentForIOS7AutoLayoutBug);
destinationTransform = CGAffineTransformMakeTranslation(0.0f,adjustmentForIOS7AutoLayoutBug);
}
[UIView animateWithDuration:_animation_duration_
delay:_animation_delay_if_you_need_one_
options:([transitionContext isInteractive] ? UIViewAnimationOptionCurveLinear : UIViewAnimationOptionCurveEaseOut)
animations:^(void)
{
targetVC.view.transform = destinationTransform;
}
completion:^(BOOL finished)
{
[transitionContext completeTransition:([transitionContext transitionWasCancelled] ? NO : YES)];
}];
}
Несколько бонусов в этом примере:
- Для того, чтобы контролировать контроллер, этот настраиваемый переход сместит нажатый toViewController.view поверх неподвижного fromViewController.view. Для pops, fromViewController.view скользит вправо и показывает неподвижный toViewController.view под ним. В целом, это всего лишь тонкий поворот на выходе контроллера iOS7 + контроллера.
- Блок завершения
[UIView animateWithDuration:...]
показывает правильный способ обработки завершенных и отмененных пользовательских переходов. Этот крошечный лакомый кусочек был классическим моментом для головы; надеюсь, что это поможет кому-то другому.
Наконец, я хотел бы отметить, что, насколько я могу судить, это проблема только с iOS7, исправленная в iOS8: мой пользовательский переход с контроллером представлений, который нарушен в iOS7, отлично работает в iOS8 без модификация. При этом вы должны убедиться, что это то, что вы тоже видите, и если это так, только запустите исправление на устройствах под управлением iOS7.x. Как вы можете видеть в приведенном выше примере кода, значение y-настройки равно 0.0f, если устройство не работает iOS7.x.
Ответ 11
В раскадровке добавьте еще одно вертикальное ограничение на главный вид сверху. У меня такая же проблема, но добавление этого ограничения помогает мне избежать ручных ограничений. Смотрите скриншот здесь ссылка
Другим решением является вычисление рамки VC... что-то вроде этого:
float y = toVC.navigationController.navigationBar.frame.origin.y + toVC.navigationController.navigationBar.frame.size.height;
toVC.view.frame = CGRectMake(0, y, toVC.view.frame.size.width, toVC.view.frame.size.height - y);
Сообщите мне, нашли ли вы лучшее решение. Я тоже борюсь с этой проблемой, и я придумал предыдущие идеи.