在通话状态栏(不能满足约束条件)

就像这个问题: 自动布局和通话状态栏和这个问题: 调整通话状态栏的大小? ,我有问题的通话状态栏搞砸了我的视图布局。

这是我的嵌套结构。 我有一个自定义模式ViewController嵌套在另一个ViewController。 每当“通话状态栏”显示(然后closures)时,就会发生以下情况:

在这里输入图像说明

这是一个在通话状态栏显示之前的样子:

在这里输入图像说明

发生错误后状态栏的背景蓝色是根视图控制器的背景颜色。

每当显示“通话状态栏”时,都会打印出以下错误信息:

Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) ( "<NSLayoutConstraint:0x7fdac6192320 V:|-(20)-[UIInputSetContainerView:0x7fdac6190a40] (Names: '|':UITextEffectsWindow:0x7fdac6061a10 )>", "<NSLayoutConstraint:0x7fdac608ebb0 'UIInputWindowController-top' V:|-(0)-[UIInputSetContainerView:0x7fdac6190a40] (Names: '|':UITextEffectsWindow:0x7fdac6061a10 )>" ) Will attempt to recover by breaking constraint <NSLayoutConstraint:0x7fdac6192320 V:|-(20)-[UIInputSetContainerView:0x7fdac6190a40] (Names: '|':UITextEffectsWindow:0x7fdac6061a10 )> Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful. Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) ( "<NSLayoutConstraint:0x7fc60b03d230 V:|-(20)-[UIInputSetContainerView:0x7fc608d22020] (Names: '|':UITextEffectsWindow:0x7fc60b171720 )>", "<NSLayoutConstraint:0x7fc60b03d2d0 UIInputSetContainerView:0x7fc608d22020.bottom == UITextEffectsWindow:0x7fc60b171720.bottom>", "<NSLayoutConstraint:0x7fc60b17c4b0 'UIInputWindowController-height' UIInputSetContainerView:0x7fc608d22020.height == UITextEffectsWindow:0x7fc60b171720.height>" ) Will attempt to recover by breaking constraint <NSLayoutConstraint:0x7fc60b03d2d0 UIInputSetContainerView:0x7fc608d22020.bottom == UITextEffectsWindow:0x7fc60b171720.bottom> Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful. 

使用FLEXdebugging工具,我可以看到

UINavigationBarBackgroundUIStatusBarForegroundView在In Call Status Bar之前重叠,但是之后UINavigationBarBackgroundUIStatusBarForegroundView之下。

这个错误只发生在我介绍模态视图控制器之后。 如果我显示在呼叫状态栏,那么问题不会发生。 显示模态视图控制器后,不能返回到根视图控制器。

我能做些什么来解决这个问题?

iOS 9.2.1,Xcode 7.2.1,ARC启用

更新3/25/2016: Xcode 7.3,iOS 9.3中仍然存在冲突。

简介:在您的应用程序的窗口层次结构中,有多个窗口被添加到应用程序窗口中。 在我的情况下,这是UITextEffectsWindowUIRemoteKeyboardWindow 。 这些窗口带有预先configuration的约束。 似乎有一个bug更新了一些垂直布局的约束,但没有其他相关的约束在同一个窗口。 这会引发debugging器中的约束冲突。 当自定义窗口被添加到窗口层次结构中,或者在模拟器和实际iOS设备上调用状态栏时,会发生这种情况。

约束优先级为1000,这表明它们是必需的约束条件。

下面的解决scheme将删除冲突的约束条件,并在通话状态栏被切换后添加回来。

编辑2/25/2016: 这两个解决scheme都不能解决打开应用程序时已经显示通话状态栏的问题。 在状态栏被注册之前发生冲突。

看起来这种约束冲突只是在第一次显示通话状态栏(这是最常见的)的情况下发生的,或者在其他情况下涉及到在关键窗口之上放置额外的自定义窗口。 我也尝试创build一个空白的单一视图应用程序,并在通话状态栏中添加任何内容,并且我得到了相同的约束冲突。

我认为这是一个错误,并在开发论坛上讨论。 Dev论坛的原文可以在这里find(正如Matty指出的那样):

https://forums.developer.apple.com/thread/16375

我想在这里扩展一点matty的答案 。 我发现这非常有帮助。 我不确定删除“所有”约束会造成什么影响,所以我删除了相互冲突的约束。 我的推理是,当debugging器通知“将试图通过打破约束来恢复”时 ,冲突约束将被打破。 所以这个约束不pipe用。

以下是我收到的约束冲突错误:

在这里输入图像说明

在这里输入图像说明

解释了约束错误后(请参阅此苹果文档以帮助: https : //developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/DebuggingTricksandTips.html )我使用下面的代码来摆脱矛盾的约束:

Objective-C的:

* AppDelegate.h

 ... @interface YourAppName : UIResponder <UIApplicationDelegate> { NSMutableDictionary *dictionaryConstraints; } ... 

* AppDelegate.m

 ... - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. dictionaryConstraints = [[NSMutableDictionary alloc] init]; return true; } - (void)application:(UIApplication *)application willChangeStatusBarFrame:(CGRect)newStatusBarFrame { NSLog(@"newStatusBarFrame: %@", NSStringFromCGRect(newStatusBarFrame)); if (newStatusBarFrame.size.height > 20.0) { for (UIWindow *window in [[UIApplication sharedApplication] windows]) { if ([window.class.description isEqual:@"UITextEffectsWindow"] || [window.class.description isEqual:@"UIRemoteKeyboardWindow"]) { NSMutableArray *constraints = [[NSMutableArray alloc] initWithCapacity:[window.constraints count]]; for (NSLayoutConstraint *constraint in window.constraints) { if (!([constraint.description rangeOfString:@"V:|-(0)-[UIInputSetContainerView"].location == NSNotFound)) { NSLog(@""); NSLog(@"%@: %@, %f, %f", window.class.description, constraint.description, constraint.priority, constraint.constant); NSLog(@""); [constraints addObject:constraint]; [window removeConstraint:constraint]; } else { nil; } } if ([constraints count] > 0) { [dictionaryConstraints setObject:constraints forKey:[NSString stringWithFormat:@"%p", window]]; } else { nil; } } else { nil; } } } else { nil; } } - (void)resetConstraints { for (UIWindow *window in [[UIApplication sharedApplication] windows]) { if ([window.class.description isEqual:@"UITextEffectsWindow"] || [window.class.description isEqual:@"UIRemoteKeyboardWindow"]) { if (dictionaryConstraints) { NSArray *keys = [dictionaryConstraints allKeys]; for (int i = 0; i < [keys count]; i++) { if ([[NSString stringWithFormat:@"%p", window] isEqualToString:keys[i]]) { [window addConstraints:[dictionaryConstraints objectForKey:keys[i]]]; } else { nil; } } } else { nil; } } else { nil; } } } - (void)application:(UIApplication *)application didChangeStatusBarFrame:(CGRect)oldStatusBarFrame { NSLog(@"oldStatusBarFrame: %@", NSStringFromCGRect(oldStatusBarFrame)); if (oldStatusBarFrame.size.height > 20.0) { if ([dictionaryConstraints count] > 0) { [self resetConstraints]; [dictionaryConstraints removeAllObjects]; } else { nil; } } else { nil; } for (UIWindow *window in [[UIApplication sharedApplication] windows]) { if ([window.class.description isEqual:@"UITextEffectsWindow"] || [window.class.description isEqual:@"UIRemoteKeyboardWindow"]) { for (NSLayoutConstraint *constraint in window.constraints) { if (!([constraint.description rangeOfString:@"V:|-(0)-[UIInputSetContainerView"].location == NSNotFound)) { NSLog(@""); NSLog(@"%@: %@, %f, %f", window.class.description, constraint.description, constraint.priority, constraint.constant); NSLog(@""); } else { nil; } } } else { nil; } } } ... 

迅速:

* AppDelegate.swift

 ... var dictionaryConstraints = [NSString : NSArray](); ... func application(application: UIApplication, willChangeStatusBarFrame newStatusBarFrame: CGRect) { print("newStatusBarFrame: \(newStatusBarFrame)") if newStatusBarFrame.size.height > 20.0 { for window in UIApplication.sharedApplication().windows { if ((window.classForCoder.description() == "UITextEffectsWindow") || (window.classForCoder.description() == "UIRemoteKeyboardWindow")) { var constraints = [NSLayoutConstraint]() for constraint in window.constraints { if (constraint.description.containsString("V:|-(0)-[UIInputSetContainerView")) { print("\(window.classForCoder.debugDescription), \(constraint.description), \(constraint.priority), \(constraint.constant)") constraints.append(constraint) window.removeConstraint(constraint) } else { //nil } } if (constraints.count > 0) { dictionaryConstraints[NSString(format: "%p", unsafeAddressOf(window))] = constraints } else { //nil } } else { //nil } } } else { //nil } } func resetConstraints() { for window in UIApplication.sharedApplication().windows { if ((window.classForCoder.description() == "UITextEffectsWindow") || (window.classForCoder.description() == "UIRemoteKeyboardWindow")) { if (dictionaryConstraints.count > 0) { let keys = Array(dictionaryConstraints.keys) for i in 0 ..< keys.count { if (NSString(format: "%p", unsafeAddressOf(window)) == keys[i]) { window.addConstraints(dictionaryConstraints[keys[i]] as! [NSLayoutConstraint]) } else { //nil } } } else { //nil } } else { //nil } } } func application(application: UIApplication, didChangeStatusBarFrame oldStatusBarFrame: CGRect) { print("oldStatusBarFrame: \(oldStatusBarFrame)") if (oldStatusBarFrame.size.height > 20.0) { if (dictionaryConstraints.count > 0) { self.resetConstraints() dictionaryConstraints.removeAll() } else { //nil } } else { //nil } for window in UIApplication.sharedApplication().windows { if ((window.classForCoder.description() == "UITextEffectsWindow") || (window.classForCoder.description() == "UIRemoteKeyboardWindow")) { for constraint in window.constraints { if (constraint.description.containsString("V:|-(0)-[UIInputSetContainerView")) { print("\(window.classForCoder.debugDescription), \(constraint.description), \(constraint.priority), \(constraint.constant)") } else { //nil } } } else { //nil } } } ... 

注意:这将保留所有不冲突的约束。 在冲突情况不复存在的情况下,将删除的冲突约束添加回来,即将通话状态栏切换出来。

更新2/25/2015: 进一步testing…

我决定在应用程序中使用两种方法来隔离不断变化的约束。 委托* .m文件:

 ... - (void)application:(UIApplication *)application willChangeStatusBarFrame:(CGRect)newStatusBarFrame { NSLog(@"New status bar frame: %@", NSStringFromCGRect(newStatusBarFrame)); for (UIWindow *window in [[UIApplication sharedApplication] windows]) { NSLog(@"%@, %@", window.description, window.constraints); } } - (void)application:(UIApplication *)application didChangeStatusBarFrame:(CGRect)oldStatusBarFrame { NSLog(@"Old status bar frame: %@", NSStringFromCGRect(oldStatusBarFrame)); for (UIWindow *window in [[UIApplication sharedApplication] windows]) { NSLog(@"%@, %@", window.description, window.constraints); } } ... 

当通话状态栏切换时,冲突约束从以下变为:

UITextEffectsWindow:

<UITextEffectsWindow:0x7fbf994cc810; frame =(0 0; 320 568); opaque = NO; autoresize = W + H; layer = <UIWindowLayer:0x7fbf994c8470>>,

(“<NSLayoutConstraint:0x7fbf99667eb0 V:| – (0) – [UIInputSetContainerView:0x7fbf99668ce0](Names:'|':UITextEffectsWindow:0x7fbf994cc810)>”

省略

“<:NSLayoutConstraint:0x7fbf9966c800'UIInputWindowController-top'V:| – (0) – [UIInputSetContainerView:0x7fbf99668ce0](Names:'|':UITextEffectsWindow:0x7fbf994cc810)>”

省略

UIRemoteKeyboardWindow:

<UIRemoteKeyboardWindow:0x7fbf994ceb80; frame =(0 0; 320 568); opaque = NO; autoresize = W + H; layer = <UIWindowLayer:0x7fbf994cf190>>,

(“<NSLayoutConstraint:0x7fbf994cfb20 V:| – (0) – [UIInputSetContainerView:0x7fbf99744ec0](Names:'|':UIRemoteKeyboardWindow:0x7fbf994ceb80)>”

省略

“<:NSLayoutConstraint:0x7fbf9966c800'UIInputWindowController-top'V:| – (0) – [UIInputSetContainerView:0x7fbf99668ce0](Names:'|':UITextEffectsWindow:0x7fbf994cc810)>”

省略

…并更改为:

UITextEffectsWindow:

<UITextEffectsWindow:0x7fbf994cc810; frame =(0 0; 320 568); opaque = NO; autoresize = W + H; layer = <UIWindowLayer:0x7fbf994c8470>>,

(“<NSLayoutConstraint:0x7fbf99667eb0 V:| – ( 20 ) – [UIInputSetContainerView:0x7fbf99668ce0](Names:'|':UITextEffectsWindow:0x7fbf994cc810)>”

省略

“<:NSLayoutConstraint:0x7fbf9966c800'UIInputWindowController-top'V:| – (0) – [UIInputSetContainerView:0x7fbf99668ce0](Names:'|':UITextEffectsWindow:0x7fbf994cc810)>”

省略

UIRemoteKeyboardWindow:

<UIRemoteKeyboardWindow:0x7fbf994ceb80; frame =(0 0; 320 568); opaque = NO; autoresize = W + H; layer = <UIWindowLayer:0x7fbf994cf190>>,

(“<NSLayoutConstraint:0x7fbf994cfb20 V:| – ( 20 ) – [UIInputSetContainerView:0x7fbf99744ec0](Names:'|':UIRemoteKeyboardWindow:0x7fbf994ceb80)>”

省略

“<:NSLayoutConstraint:0x7fbf9966c800'UIInputWindowController-top'V:| – (0) – [UIInputSetContainerView:0x7fbf99668ce0](Names:'|':UITextEffectsWindow:0x7fbf994cc810)>”

省略

要理解视觉格式语言,请阅读https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html

垂直布局V:[topField] -XX- [bottomField]“格式的垂直布局约束从…变为…

NSLayoutConstraint:0x7fbf99667eb0 V:| – (0) – [UIInputSetContainerView:0x7fbf99668ce0]

至…

NSLayoutConstraint:0x7fbf99667eb0 V:| – (20) – [UIInputSetContainerView:0x7fbf99668ce0]

…对于这两个窗口:UITextEffectsWindow和UIRemoteKeyboardWindow; 不过,…

NSLayoutConstraint:0x7fbf9966c800'UIInputWindowController-top'V:| – (0) – [UIInputSetContainerView:0x7fbf99668ce0]

…才不是。

所以从我可以推断,窗口调整其约束来说明添加的通话状态栏,但UIInputWindowController没有。 因此,这个约束冲突是有争议的。

但情节变厚了

在通话状态栏切换导致的初始约束冲突之后,约束不会改变,优先级相同,但是进入或离开通话状态栏的进一步切换不会产生约束冲突。 但是,这只是因为最初的冲突已经被抛出。

希望这可以帮助! 干杯。

感谢所有原始贡献者。

@matty的快速版回答:

 func application(application: UIApplication, willChangeStatusBarFrame newStatusBarFrame: CGRect) { for window in UIApplication.sharedApplication().windows { if window.dynamicType.self.description().containsString("UITextEffectsWindow") { window.removeConstraints(window.constraints) } } } 

类似的问题也可以在这里find: https : //forums.developer.apple.com/thread/20632

我试图通过在我的AppDelegate实施下面的代码段从该主题build议的解决方法。 它似乎摆脱了约束的错误,但我非常不愿意发布一个应用程序,因为它肯定是一个苹果的错误。

重现步骤:

  1. 在Xcode 7.1中创build一个新的单一视图应用程序
  2. 在iOS 9.1模拟器中运行应用程序
  3. 切换通话栏(CMD + Y)
  4. 你会看到“无法同时满足约束条件”。 错误。

为了摆脱约束的错误:

 - (void)application:(UIApplication *)application willChangeStatusBarFrame:(CGRect)newStatusBarFrame { for(UIWindow *window in [[UIApplication sharedApplication] windows]) { if([window.class.description isEqual:@"UITextEffectsWindow"]) { [window removeConstraints:window.constraints]; } } } 

就我个人而言,我只使用上面的代码片断来确保约束错误不会导致我遇到的其他约束问题,即在应用程序在后台启动时出现黑屏。 结果并非如此。

以上所有都不是简单的解决scheme 当我在viewDidLoad中添加子视图时遇到了同样的麻烦。 我通过将代码移动到viewDidAppear中来了解它。 仅供参考。