Swift за замовчуванням AlertViewController порушує обмеження


82

Я намагаюся використовувати за замовчуванням AlertViewController зі стилем .actionSheet . З якоїсь причини попередження викликає помилку обмеження . Поки alertController не запускається (відображається) за допомогою кнопки, помилок обмежень у всьому поданні не виникає. Можливо, це помилка Xcode?

Точна помилка, яку я отримую, виглядає так:

2019-04-12 15:33:29.584076+0200 Appname[4688:39368] [LayoutConstraints] 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. 
(
    "<NSLayoutConstraint:0x6000025a1e50 UIView:0x7f88fcf6ce60.width == - 16   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x6000025a1e50 UIView:0x7f88fcf6ce60.width == - 16   (active)>

Це код, який я використовую:

@objc func changeProfileImageTapped(){
        print("ChangeProfileImageButton tapped!")
        let alert = UIAlertController(title: "Change your profile image", message: nil, preferredStyle: .actionSheet)

        alert.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: nil))
        alert.addAction(UIAlertAction(title: "Online Stock Library", style: .default, handler: nil))
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
        alert.view.tintColor = ColorCodes.logoPrimaryColor

        self.present(alert, animated: true)
    }

Як бачите, це дуже елементарно . Ось чому мене дуже бентежить дивна поведінка, яку я отримую, оскільки ця реалізація за замовчуванням не повинна викликати помилок, так?

Результат, який я отримую

Хоча, порушуючи обмеження, попередження відображається належним чином на всіх розмірах екрану, я був би дуже вдячний за будь-яку допомогу, яку я отримаю.


Ви будуєте будь-які інші обмеження в коді?
Sh_Khan

@Sh_Khan ні, ні. Я маю на увазі, очевидно, що у мене є інші погляди під поданням сповіщення, але вони працюють нормально і не викликають жодних помилок обмеження. Але я нічого не змінив на обмеженнях
AlertView

1
@linus_hologram, чи спричиняє щось на екрані візуальний збій? якщо так, то що? якщо ні , не витрачайте свій час на спроби виправити щось, що навіть не зламалося.
холекс

1
Журнал обмежень не завжди відповідає дійсності, іноді він створює оманливі речі
Sh_Khan,

1
Я вивчив проблему з налагоджувачем, це динамічно додане обмеження ... Я не можу знайти це обмеження перед тим, як представляти UIAlertController ... Див. Більше у моїй відповіді нижче
Agisight

Відповіді:


42

Ця помилка не є критичною, здається, виправлена ​​помилка з боку Apple. Це обмеження з’являється у стилі анімації відразу після презентації. введіть тут опис зображенняЯ намагався зловити і змінити це (змінити значення, відносини, пріоритет) перед поданням - успіху не було через це динамічно додані обмеження.

Коли ви вимикаєте анімацію self.present(alert, animated: false)та використовуєте alert.view.addSubview(UIView())- помилка зникає. Я не можу пояснити, але це працює!

let alert = UIAlertController(title: "Change your profile image", message: nil, preferredStyle: .actionSheet)

alert.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: nil))
alert.addAction(UIAlertAction(title: "Online Stock Library", style: .default, handler: nil))
let cancel = UIAlertAction(title: "Cancel", style: .destructive, handler: nil)

alert.addAction(cancel)
alert.view.addSubview(UIView()) // I can't explain it, but it works!

self.present(alert, animated: false)

1
як ця відповідь пов'язана з проблемою обмежень, яку має ОП?
holex

3
вимкнути анімацію + alert.view.addSubview(UIView())у мене спрацювало. Але без анімації це настільки дивно ... Все одно дякую за це рішення.
Тіеда Вей

1
Так, я пояснюю це помилкою від Apple. Я це пояснив. Але якщо ви можете вирішити проблему, ви можете спробувати отримати необхідне обмеження та змінити його. Я думаю, що це не гарна ідея, тому що це помилка.
Agisight

1
Я думаю, що додавання підпрогляду змушує обмеження авторозкладки перераховуватися, мовчачи попередження.
FormigaNinja

3
Ця проблема виникає, коли animationвстановлено значення true. Я думаю, що це внутрішня помилка, і її можна ігнорувати, оскільки actoinSheet відображається належним чином. Я радий, що зміг звузити це до першопричини, спочатку я навчив, що це мій код.
Джеррі Окафор

45

Далі видаляється попередження без необхідності вимикати анімацію. І якщо припустити, що Apple врешті-решт усуне першопричину попередження, це не повинно порушити нічого іншого.

extension UIAlertController {
    func pruneNegativeWidthConstraints() {
        for subView in self.view.subviews {
            for constraint in subView.constraints where constraint.debugDescription.contains("width == - 16") {
                subView.removeConstraint(constraint)
            }
        }
    }
}

Потім це можна використовувати так:

// After all addActions(...), just before calling present(...)
alertController.pruneNegativeWidthConstraints()

1
Відмінно працює, більше не забруднення колоди! 🥳
Клаас,

Це насправді викликає більше проблем зараз (iOS 13.2.2), не знаючи, як це працювало в попередніх версіях. Причиною того, що UIAlertController відображається закріпленим у верхній частині екрана.
jovanjovanovic

@jovanjovanovic прикро чути, що ти переживаєш це, але я не можу відтворити те, що ти описуєш. Це рішення досі залишається тим, яке видаляє попередження без негативного впливу. Якщо у вас є зразок коду, який може відтворити те, що ви описуєте, поділіться.
jims1103

Я використовую цю програму відразу після створення сповіщення, але, здається, не працює! Де найкраще розмістити код?
Lucas Tegliabue

@LucasTegliabue використовує його перед тим, як представляти контролер оповіщення, як let alertController = UIAlertController () alertController.pruneNegativeWidthConstraints () self.present (alertController, анімоване: true, завершення: nil)
Джек

18

Це нова помилка у версіях iOS:

  • 12.2
  • 12.3
  • 12.4
  • 13,0
  • 13.1
  • 13.2
  • 13.2.3
  • 13.3
  • 13.4
  • 13.4.1
  • 13.5
  • 13.6
  • 14,0

Єдине, що ми можемо зробити, це подати звіт про помилку в Apple (я щойно зробив це, і ти теж повинен).

Я спробую оновити відповідь на нову версію (и) iOS, коли вона з’явиться.


8

Додавання до цієї відповіді ... Здається, це видаляє проблему для мене і не вимагає змін у існуючому коді.

extension UIAlertController {
    override open func viewDidLoad() {
        super.viewDidLoad()
        pruneNegativeWidthConstraints()
    }

    func pruneNegativeWidthConstraints() {
        for subView in self.view.subviews {
            for constraint in subView.constraints where constraint.debugDescription.contains("width == - 16") {
                subView.removeConstraint(constraint)
            }
        }
    }
}

2

Безпечне рішення

Не слід видаляти обмеження, оскільки воно буде використовуватися в майбутньому з правильним значенням.

В якості альтернативи ви можете змінити його константу на додатне значення:

class PXAlertController: UIAlertController {
    override func viewDidLoad() {
        super.viewDidLoad()

        for subview in self.view.subviews {
            for constraint in subview.constraints {
                if constraint.firstAttribute == .width && constraint.constant == -16 {
                    constraint.constant = 10 // Any positive value
                }
            }
        }
    }
}

А потім для ініціалізації контролера використовуйте:

let controller = PXAlertController(title: "Title", message: "Message", preferredStyle: .actionSheet)

Я без успіху спробував усі запропоновані рішення, застосовні до Swift. Я використовую XCode 11.5, компілюючи для iOS 13.0, працює в iPhone Xs Max. Все ще отримую "<NSLayoutConstraint:0x28002b930 UIView:0x102e32420.width == - 16 (active)>". Я нічого не можу допомогти, якщо не сказати, що проблема все ще є. Як хтось запропонував, я просто ігноруватиму помилку, але мені це не подобається.
Нікола Мінґотті,

це взагалі не працює. Рішення від RichW працює принаймні на iOS 13
Вячаслав Герчиков,

@VyachaslavGerchicov Будьте обережні, але я вважаю, що старіші версії iOS зазнають збою при зміні необхідного обмеження на необов’язкове під час виконання. stackoverflow.com/questions/31186187/…
Джош Бернфельд

1

Альтернативний спосіб уникнути помилки NSLayoutConstraint - використовувати preferredStyle: .alertзамість preferredStyle: .actionSheet. Це працює без попередження, але меню відображатиметься модально.


0

Рішення для Objective-C:

  1. Підклас власного контролера сповіщень від UIAlertController
  2. Визначте функцію чорносливу, як у попередній відповіді

    @implementation TemplateAlertController
    
    -(void) viewDidLoad {
    
        [super viewDidLoad];
        [self mPruneNegativeWithConstraints];
    }
    
    -(void) mPruneNegativeWithConstraints {
    
        for (UIView* iSubview in [self.view subviews]) {
            for (NSLayoutConstraint* iConstraint in [iSubview constraints]) {
                if ([iConstraint.debugDescription containsString:@"width == - 16"]) {
                    [iSubview removeConstraint:iConstraint];
                }
            }
        }
    }
    
    @end
    

0

Цікаві ідеї тут. Особисто мені не подобається ідея видалення обмеження або зміни його значення (розміру).

Оскільки проблема залежать від того, що резолюція обмеження буде змушена перейти в положення, коли воно повинно порушити встановлене обмеження (пріоритет 1000), менш жорстокий підхід полягає лише у тому, щоб сказати структурі, що це обмеження може бути порушено у разі потреби.

Отже (на основі класу Джоша "Безпечний"):

class PXAlertController: UIAlertController {
    override func viewDidLoad() {
        super.viewDidLoad()
        tweakProblemWidthConstraints()
    }
    
    func tweakProblemWidthConstraints() {
        for subView in self.view.subviews {
            for constraint in subView.constraints {
                // Identify the problem constraint
                // Check that it's priority 1000 - which is the cause of the conflict.
                if constraint.firstAttribute == .width &&
                    constraint.constant == -16 &&
                    constraint.priority.rawValue == 1000 {
                    // Let the framework know it's okay to break this constraint
                    constraint.priority = UILayoutPriority(rawValue: 999)
                }
            }
        }
    }
}

Це має ті переваги, що воно не змінює будь-які розміри макета, а також має великі шанси на хороше поводження у разі виправлення в рамках.

Тестується в симуляторі iPhone SE (що дало мені мою початкову проблему) - налагодження, пов'язане з обмеженнями, пішло.


Працює в iOS 13, але не в iOS 12. Пробував на симуляторі iPhone X
Вячаслав Герчиков

@RichW це хороший підхід. Будьте обережні, я вважаю, що старіші версії iOS зазнають збою при зміні необхідного обмеження на необов’язкове під час виконання. stackoverflow.com/questions/31186187/…
Джош Бернфельд

0

Якщо ви хочете зберегти анімацію та всі обмеження, вам слід знайти негативне обмеження та зробити його позитивним перед тим, як представляти контролер попереджень.

// Find negative constraint and make it positive
for subview in alert.view.subviews {
    for constraint in subview.constraints {
        if constraint.constant < 0 {
            constraint.constant = -constraint.constant
        }
    }
}

// Present alert controller
present(alert, animated: true)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.