通话状态栏(无法满足约束条件)

32
就像这个问题:Auto Layout and in-call status bar 和这个问题:Resize for in-call status bar?,我遇到了通话状态栏捣乱我的视图布局的问题。
这是我的嵌套结构。我有一个自定义模态视图控制器,它嵌套在另一个视图控制器中。每当显示通话状态栏(然后关闭),就会发生以下情况: enter image description here 这是在显示通话状态栏之前应该看起来的图片: enter image description here 在错误发生后,状态栏背景蓝色是根视图控制器的背景颜色。
每当显示通话状态栏时,都会打印出以下错误:
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.

使用FLEX调试工具,我可以看到,在呼叫状态栏之前,UINavigationBarBackgroundUIStatusBarForegroundView重叠,但在呼叫状态栏之后,UINavigationBarBackgroundUIStatusBarForegroundView下面。
这个问题只会出现在我展示模态视图控制器之后。如果我显示呼叫状态栏,则不会出现此问题。在展示模态视图控制器后,您不能返回到根视图控制器。
我应该做什么来解决这个问题?

只是确认你不是孤单的。我自己经常使用个人热点,所以每次热点处于蓝色状态栏的纵向方向时,都会出现这个自动布局错误。但是当以横向模式启动时,当蓝色条只有20像素时,就不会发生这个错误。 - Stanislav Dvoychenko
我一直在处理这个完全相同的错误已经有一段时间了。不幸的是,我还没有找到解决方法。我唯一能想到的解决办法是在显示自定义模态视图时完全隐藏状态栏,然后在关闭模态视图时重新显示状态栏。 - denvdancsk
很好的问题。这些解决方案中有没有帮到您?请选出正确的答案。 - serge-k
不,我无法解决这个问题。看起来如果我不使用ModalViewController,问题似乎就消失了,所以我稍微改变了我的视图层次结构。 - Will Hua
4个回答

12
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进行一些补充,我觉得这很有帮助。我不确定移除“所有”约束会带来什么影响,所以我只移除了冲突的约束。我的理由是冲突的约束将被打破,因为调试器会提示“将尝试通过打破约束来恢复”;所以该约束将没有任何作用。
以下是我收到的约束冲突错误:

enter image description here

enter image description 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
{
    // 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;
        }
    }
}

...

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
                    {
                        //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
        }
    }
}

...

注意:这将保留所有未冲突的约束,并在不再存在冲突情况时将已移除的冲突约束重新添加回去,即在呼叫状态栏被切换出去后。
更新于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不会。因此,约束冲突就出现了。
但情节变得更加复杂...
在呼叫状态栏切换导致的初始约束冲突之后,约束不会改变,优先级也相同,但是进一步切换呼叫状态栏时不会出现约束冲突。但是,这只是因为初始冲突已经被抛出。 希望这有所帮助!干杯。
感谢所有原始贡献者。

3
请不要认为我因为花费大量时间来解决一个很可能是错误的警告而变得疯狂。无论如何,我需要学习关于NSLayoutConstraints和布局格式语言。 - serge-k
@HassanTaleb 我还没有。给我几天时间,我会学习这门语言。也许其他人有?你看到上面Matty的回答了吗?它可能作为一个线索... https://dev59.com/vFwY5IYBdhLWcg3wAj09#33411423 - serge-k
没有任何切换,第一次启动应用程序时,我得到了以下信息: newStatusBarFrame: (0.0, 0.0, 0.0, 20.0) oldStatusBarFrame: (0.0, 0.0, 0.0, 0.0) - Hassan Taleb
@serge-k 为什么你在所有的 else 块中都放置了 nil?我很好奇这是一种风格选择,还是你有其他原因,因为我之前从未见过这样做。谢谢。 - Ryan Maloney
@RyanMaloney 嘿,Ryan,对我来说这是一种风格选择。我从同一位教授的C/C++课程中学到了这个技巧,已经用了三年了。这对我很有帮助,因为它更容易识别嵌套的if-else语句。但是现代的代码检查工具可以帮助解决这个问题。同时,我也不确定编译器会如何解释省略条件。如果在版本升级后重新编译时,编译器会插入一些奇怪的东西,那该怎么办呢?省略条件的执行时间是否比不省略的长,或者它们是相同的,或者省略的情况更好?也许编译器会忽略它。 - serge-k
显示剩余11条评论

9

@matty的Swift版本:

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

快速而简单,当应用程序正在运行时显示呼叫状态栏时完成工作,但如果在应用程序启动之前可见,则仍将引发警告(如@serge-k已经注意到的那样)。 - lshepstone

3

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

我尝试了那个帖子中建议的解决方法,通过在我的AppDelegate中实现下面的片段。它似乎摆脱了约束错误,但我非常不愿意发布这样的应用程序,因为它肯定是一个苹果的Bug。

复制步骤:

  1. 在Xcode 7.1中创建新的单视图应用
  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];
        }  
    }
}

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


根据导致问题的窗口和约束条件,您可以调整代码以包括其他窗口类和约束条件,例如UIRemoteKeyboardWindow。或者指定要删除的特定约束条件。 - serge-k
2
我不建议这样做。你正在移除苹果创建的约束条件。问题似乎是一个漏洞,而你并不知道苹果何时会修复它,当他们修复时,你可能会移除本应存在的约束条件。 - ThomasCle

0

所有上述解决方案似乎都不是简单的。 当我在viewDidLoad中添加子视图时,我遇到了同样的问题。 通过将代码移动到viewDidAppear中,我解决了这个问题。 供参考。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接