web-dev-qa-db-ja.com

通話ステータスバー内(制約を満たすことができません)

この質問のように: 自動レイアウトと通話中のステータスバー そしてこの質問: 通話中のステータスバーのサイズを変更しますか? 、通話中のステータスバーに問題がありますビューレイアウトを台無しにします。

これが私のネストされた構造です。別のViewController内にネストされたカスタムモーダルViewControllerがあります。通話中のステータスバーが表示される(そして閉じられる)と、次のようになります。

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を下回っています。

このバグは、Modal ViewControllerを提示した後にのみ発生します。通話中のステータスバーを表示しても、問題は発生しません。 Modal View Controllerが表示された後は、ルートViewControllerに戻ることはできません。

これを修正するにはどうすればよいですか?

32

iOS 9.2.1、Xcode 7.2.1、ARC対応

UPDATE 3/25/2016:競合はXcode 7.3、iOS9.3にまだ存在します。

概要:アプリケーションのウィンドウ階層には、アプリケーションウィンドウに追加されるさまざまなウィンドウがあります。私の場合、これはUITextEffectsWindowUIRemoteKeyboardWindowでした。これらのウィンドウには、事前構成された制約があります。いくつかの垂直レイアウト制約を更新するバグがあるようですが、同じウィンドウの他の関連する制約は更新しません。これにより、デバッガーで制約の競合が発生します。これは、シミュレータと実際のiOSデバイスの両方で、カスタムウィンドウがウィンドウ階層に追加されたとき、または通話中のステータスバーが切り替えられたときに発生します。

制約は優先度1000であり、これはそれらが必須の制約であることを示しています。

以下の解決策は、競合する制約を削除し、通話中のステータスバーが切り替えられたら再び追加します。

2016年2月25日編集:どちらの解決策も、アプリを開いたときに通話中のステータスバーがすでに表示されているという問題を解決しません。ステータスバーの変更が登録される前に競合が発生します。

この制約の競合は、通話中のステータスバーが最初に表示されたとき(これが最も一般的です)、またはキーウィンドウの上に配置される追加のカスタムウィンドウの表示を含む他のシナリオでのみ発生するようです。 また、空白のシングルビューアプリケーションを作成し、通話中のステータスバーに何も切り替えを追加しないでみましたが、同じ制約の競合が発生しました。

これはバグであり、開発フォーラムで議論されていると思います。 Devフォーラムの元の記事はここにあります(mattyが指摘したように):

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

ここでのマットの答え について少し詳しく説明したいと思います。とても役に立ちました。 「すべての」制約を削除するとどのような影響が生じるかわからないため、競合する制約のみを削除しました。私の推論は、デバッガーが通知すると、競合する制約がとにかく破られるということです "制約を破ることによって回復を試みます";したがって、制約はとにかく目的を果たしません。

私が受け取っていた制約の競合エラーは次のとおりです。

enter image description here

enter image description here

制約エラーを解釈した後(これを参照してくださいAppleドキュメント: 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
        }
    }
}

...

注:これにより、競合しないすべての制約が保持されます。そして、競合する状況がなくなった後、つまり、通話中のステータスバーが切り替わった後、削除された競合する制約を追加し直します。

2015年2月25日更新:さらなるテスト...

アプリで2つの方法を使用して、変化する制約を分離することにしました。デリゲート* .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;フレーム=(0 0; 320 568);不透明=いいえ;自動サイズ変更= W + H;レイヤー= <UIWindowLayer:0x7fbf994c8470 >>、

( "<NSLayoutConstraint:0x7fbf99667eb0 V:| )-[UIInputSetContainerView:0x7fbf99668ce0](名前: '|':UITextEffectsWindow:0x7fbf994cc810)>"、

...省略

"<NSLayoutConstraint:0x7fbf9966c800'UIInputWindowController-top 'V:|-(0)-[UIInputSetContainerView:0x7fbf99668ce0](名前:' | ':UITextEffectsWindow:0x7fbf994cc810)>"、

...省略

UIRemoteKeyboardWindow:

<UIRemoteKeyboardWindow:0x7fbf994ceb80;フレーム=(0 0; 320 568);不透明=いいえ;自動サイズ変更= W + H;レイヤー= <UIWindowLayer:0x7fbf994cf190 >>、

( "<NSLayoutConstraint:0x7fbf994cfb20 V:|-(0)-[UIInputSetContainerView:0x7fbf99744ec0](名前: '|':UIRemoteKeyboardWindow:0x7fbf994ceb80)>"、

...省略

"<NSLayoutConstraint:0x7fbf9966c800'UIInputWindowController-top 'V:|-(0)-[UIInputSetContainerView:0x7fbf99668ce0](名前:' | ':UITextEffectsWindow:0x7fbf994cc810)>"、

...省略

...そして変更:

UITextEffectsWindow:

<UITextEffectsWindow:0x7fbf994cc810;フレーム=(0 0; 320 568);不透明=いいえ;自動サイズ変更= W + H;レイヤー= <UIWindowLayer:0x7fbf994c8470 >>、

( "<NSLayoutConstraint:0x7fbf99667eb0 V:|-(20)-[UIInputSetContainerView:0x7fbf99668ce0](名前: '|':UITextEffectsWindow:0x7fbf994cc810)>" 、

...省略

"<NSLayoutConstraint:0x7fbf9966c800'UIInputWindowController-top 'V:|-(0)-[UIInputSetContainerView:0x7fbf99668ce0](名前:' | ':UITextEffectsWindow:0x7fbf994cc810)>"、

...省略

UIRemoteKeyboardWindow:

<UIRemoteKeyboardWindow:0x7fbf994ceb80;フレーム=(0 0; 320 568);不透明=いいえ;自動サイズ変更= W + H;レイヤー= <UIWindowLayer:0x7fbf994cf190 >>、

( "<NSLayoutConstraint:0x7fbf994cfb20 V:|-(20)-[UIInputSetContainerView:0x7fbf99744ec0](名前: '|':UIRemoteKeyboardWindow:0x7fbf994ceb80)>" 、

...省略

"<NSLayoutConstraint:0x7fbf9966c800'UIInputWindowController-top 'V:|-(0)-[UIInputSetContainerView:0x7fbf99668ce0](名前:' | ':UITextEffectsWindow:0x7fbf994cc810)>"、

...省略

ビジュアルフォーマット言語を理解するには、---(https://developer.Apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html をお読みください。

「VerticalLayoutV:[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は調整しません。したがって、制約の競合が発生します。

しかし、プロットは厚くなります...

通話中のステータスバーの切り替えによって初期制約が競合した後、制約は変更されず、優先度は同じですが、通話中のステータスバーをさらに切り替えても、制約の競合は発生しません。 しかし、これは最初の競合がすでにスローされたためです。

お役に立てれば!乾杯。

すべての元の貢献者に感謝します。

12
serge-k

@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)
        }
    }
}
9
jfgrang

同様の問題はここでも見つけることができます: https://forums.developer.Apple.com/thread/20632

AppDelegateに以下のスニペットを実装することにより、そのトピックから提案された回避策を試みました。制約エラーを取り除くようですが、これは間違いなくAppleバグであるため、これを使用してアプリをリリースすることには非常に消極的です。

再現する手順:

  1. Xcode7.1で新しいシングルビューアプリケーションを作成します
  2. IOS9.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];
        }  
    }
}

個人的には、上記のスニペットを使用して、制約エラーが他の制約の問題を引き起こしていないことを確認しました。つまり、アプリがバックグラウンドで起動されたときの黒い画面です。それは事実ではないことが判明しました。

3
matty

上記のすべては単純な解決策ではないようでした。 viewDidLoadにサブビューを追加したときに、同じ問題が発生しました。コードをviewDidAppearに移動することでそれを回避しました。ご参考までに。

0
firebear