В строке состояния вызова (Невозможно удовлетворить ограничения)

Точно так же, как этот вопрос: Автоматическая разметка и строка состояния в вызове и этот вопрос: Изменить размер строки состояния в вызове?, У меня проблемы с строкой состояния In Call, которая испортила мой внешний вид.

Вот моя вложенная структура. У меня есть пользовательский модальный ViewController, который вложен в другой ViewController. Всякий раз, когда строка состояния входящего вызова отображается (а затем закрывается), происходит следующее:

введите описание изображения здесь

Вот изображение того, как оно должно выглядеть перед отображением строки состояния In Call:

введите описание изображения здесь

Синий фон строки состояния после возникновения ошибки - это цвет фона корневого контроллера представления.

Всякий раз, когда отображается строка состояния In Call, выводится следующая ошибка:

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, я вижу, что

UINavigationBarBackground а также UIStatusBarForegroundView перекрываются перед строкой состояния In Call, однако впоследствии UINavigationBarBackground ниже UIStatusBarForegroundView,

Эта ошибка возникает только ПОСЛЕ того, как я представляю Modal ViewController. Если я показываю строку состояния In Call, то проблема не возникает. Вы не можете вернуться к корневому контроллеру представления после того, как отображается Modal ViewController.

Что я могу сделать, чтобы это исправить?

4 ответа

iOS 9.2.1, Xcode 7.2.1, ARC включен

ОБНОВЛЕНИЕ 25.03.2016: Конфликт все еще существует в Xcode 7.3, iOS 9.3.

Описание: В иерархии окон для вашего приложения есть различные окна, которые добавляются в окно приложения. В моем случае это был UITextEffectsWindow и UIRemoteKeyboardWindow, Эти окна поставляются с предварительно настроенными ограничениями. Кажется, есть ошибка, которая обновляет некоторые ограничения вертикальной компоновки, но не другие связанные ограничения для того же окна. Это вызывает конфликт ограничений в отладчике. Это происходит, когда пользовательское окно добавляется в иерархию окон или когда строка состояния входящего вызова включается или выключается как на симуляторе, так и на реальном устройстве iOS.

Ограничения имеют приоритет 1000, это указывает на то, что они являются обязательными ограничениями.

Приведенное ниже решение удалит конфликтующее ограничение и добавит его обратно после выключения строки состояния в вызове.

РЕДАКТИРОВАТЬ 25.02.2016: Ни одно из решений не решает проблему отображения строки состояния при вызове при открытии приложения. Конфликт происходит до регистрации изменения в строке состояния.

Похоже, что этот конфликт ограничений возникает только при первом отображении строки состояния в вызове (это наиболее часто встречается) или в других сценариях, включающих представление дополнительного настраиваемого окна, которое будет располагаться поверх ключевого окна. Я также попытался просто создать пустое приложение с одним представлением и ничего не переключать в строке состояния вызова, и я получил тот же конфликт ограничений.

Я думаю, что это ошибка и обсуждается на форумах разработчиков. Оригинальную статью с форумов Dev можно найти здесь (как указал Мэтти):

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

Я хотел бы немного расширить ответ Мэтти здесь. Что я нашел очень полезным. Я не уверен, какое влияние окажет удаление "всех" ограничений, поэтому я удалил только конфликтующие ограничения. Я полагаю, что конфликтующее ограничение будет в любом случае нарушено, поскольку отладчик сообщает: "Попытка восстановления путем нарушения ограничения"; таким образом, ограничение не будет служить цели в любом случае.

Вот ошибки конфликта ограничений, которые я получал:

После интерпретации ошибок ограничений (см. Этот яблочный документ, чтобы помочь с этим: 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
        }
    }
}

...

Примечание. При этом сохраняются все не противоречащие ограничения. И добавляет удаленные конфликтующие ограничения обратно после того, как конфликтной ситуации больше нет, а именно, строка состояния в вызове переключается.

ОБНОВЛЕНИЕ 25.02.2015: Дальнейшее тестирование...

Я решил изолировать меняющиеся ограничения, используя два метода в приложении. делегат *.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:

>,

("",

... опущено

"

... опущено)

UIRemoteKeyboardWindow:

>,

("",

... опущено

"

... опущено)

... и изменяется на:

UITextEffectsWindow:

>,

("20) - [UIInputSetContainerView: 0x7fbf99668ce0] (Имена: '|':UITextEffectsWindow: 0x7fbf994cc810)>",

... опущено

"

... опущено)

UIRemoteKeyboardWindow:

>,

("20) - [UIInputSetContainerView: 0x7fbf99744ec0] (Имена: '|': UIRemoteKeyboardWindow: 0x7fbf994ceb80)>",

... опущено

"

... опущено)

Чтобы понять язык визуального форматирования, прочитайте 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 - нет. Таким образом, конфликт ограничений возникает.

Но сюжет утолщается...

После конфликта начальных ограничений, вызванного переключением строки состояния входящих вызовов, ограничения не изменяются, и приоритет остается тем же, но конфликт ограничений не возникает при дальнейшем переключении строки состояния входящего вызова в исходящее или исходящее. Но это только потому, что первоначальный конфликт уже был брошен.

Надеюсь это поможет! Приветствия.

Спасибо всем оригинальным авторам.

Версия Swift от @matty answer:

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

Подобную проблему также можно найти здесь: https://forums.developer.apple.com/thread/20632

Я попытался предложить обходной путь из этой темы, применив приведенный ниже фрагмент в моем AppDelegate. Кажется, что это избавляет от ошибок ограничений, но я очень не хочу выпускать приложение с этим, так как это, безусловно, ошибка Apple.

Действия по воспроизведению:

  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];
        }  
    }
}

Лично я использовал приведенный выше фрагмент, чтобы убедиться, что ошибки ограничения не вызывали других проблем с ограничениями, а именно: черные экраны, когда приложение запускалось в фоновом режиме. Оказалось, что это не так.

Все вышеперечисленное казалось не простым решением. Я столкнулся с той же проблемой, когда я добавил подпредставление в viewDidLoad. Я обошел его, переместив код в viewDidAppear. FYI.

Другие вопросы по тегам