iOS 9.2.1,Xcode 7.2.1,启用ARC
更新3/25/2016:在Xcode 7.3,iOS 9.3中仍存在冲突。
摘要:在应用程序的窗口层次结构中,有各种窗口添加到应用程序窗口中。在我的情况下,这是UITextEffectsWindow和UIRemoteKeyboardWindow。这些窗口带有预配置的约束。似乎存在一个bug,它会更新一些垂直布局约束,但不会更新同一窗口的其他相关约束。这将在调试器中引发约束冲突。当自定义窗口被添加到窗口层次结构中或者呼叫状态栏在模拟器和实际iOS设备上切换时,就会发生这种情况。
这些约束的优先级为1000,这表示它们是必需的约束。
以下解决方案将删除冲突约束,并在呼叫状态栏切出后重新添加它。
编辑2/25/2016:两种解决方案都无法解决打开应用程序时已经显示呼叫状态栏的问题。冲突发生在状态栏更改被注册之前。
It appears that this constraint conflict happens only the first time the in-call status bar is shown (this is most common), or in other scenarios involving the presentation of an additional custom window that would sit on top of the key window.
I also tried just creating a blank single view application and with adding nothing toggling in the in-call status bar, and I got the same constraint conflict.
I think it is a bug and is discussed on the Dev forums. The original article from the Dev forums can be found here (as matty pointed out):
https://forums.developer.apple.com/thread/16375
我想对
matty's answer here进行一些补充,我觉得这很有帮助。我不确定移除“所有”约束会带来什么影响,所以我只移除了冲突的约束。我的理由是冲突的约束将被打破,因为调试器会提示
“将尝试通过打破约束来恢复”;所以该约束将没有任何作用。
以下是我收到的约束冲突错误:
在解释约束错误后(可以参考这篇苹果文档: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
{
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;
}
}
}
...
Swift:
*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
{
}
}
if (constraints.count > 0)
{
dictionaryConstraints[NSString(format: "%p", unsafeAddressOf(window))] = constraints
}
else
{
}
}
else
{
}
}
}
else
{
}
}
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
{
}
}
}
else
{
}
}
else
{
}
}
}
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
{
}
}
else
{
}
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
{
}
}
}
else
{
}
}
}
...
注意:这将保留所有未冲突的约束,并在不再存在冲突情况时将已移除的冲突约束重新添加回去,即在呼叫状态栏被切换出去后。
更新于2015年2月25日:进一步测试...
我决定使用应用程序代理*.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)>",
...omitted
"< NSLayoutConstraint:0x7fbf9966c800 'UIInputWindowController-top' V:|-(0)-[UIInputSetContainerView:0x7fbf99668ce0] (Names: '|':UITextEffectsWindow: 0x7fbf994cc810)>",
...omitted )
...并更改为:
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 )>",
...omitted
"<NSLayoutConstraint:0x7fbf9966c800 'UIInputWindowController-top' V:|-(0)-[UIInputSetContainerView:0x7fbf99668ce0] (Names:
'|':UITextEffectsWindow:0x7fbf994cc810 ) >",
...omitted)
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 ) >",
...omitted
"< NSLayoutConstraint:0x7fbf9966c800 'UIInputWindowController-top' V:|-(0)-[UIInputSetContainerView:0x7fbf99668ce0] (Names: '|':UITextEffectsWindow:0x7fbf994cc810 )>",
...omitted )
这是一个包含约束条件的代码块,其中涉及到窗口、输入框等内容。
要理解可视化格式语言,请阅读
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html
在格式“垂直布局V:[topField] -XX- [bottomField]”中,竖直布局约束似乎从...更改为...
NSLayoutConstraint:0x7fbf99667eb0
V:| -(0) - [UIInputSetContainerView:0x7fbf99668ce0]
对于两个窗口:UITextEffectsWindow和UIRemoteKeyboardWindow; 然而...
NSLayoutConstraint:0x7fbf9966c800'UIInputWindowController-top'
V:| -(0) - [UIInputSetContainerView:0x7fbf99668ce0]
不会改变。
所以我可以推断,窗口会调整其约束以适应添加的呼叫状态栏,但UIInputWindowController不会。因此,约束冲突就出现了。
但情节变得更加复杂...
在呼叫状态栏切换导致的初始约束冲突之后,约束不会改变,优先级也相同,但是进一步切换呼叫状态栏时不会出现约束冲突。
但是,这只是因为初始冲突已经被抛出。
希望这有所帮助!干杯。
感谢所有原始贡献者。