Schneller Standard-AlertViewController, der Einschränkungen verletzt

78

Ich versuche, einen Standard-AlertViewController mit dem Stil .actionSheet zu verwenden . Aus irgendeinem Grund verursacht die Warnung einen Einschränkungsfehler . Solange der alertController nicht über eine Schaltfläche ausgelöst (angezeigt) wird, gibt es in der gesamten Ansicht keine Einschränkungsfehler. Könnte es sein, dass dies ein Fehler von Xcode ist?

Der genaue Fehler, den ich bekomme, sieht folgendermaßen aus:

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)>

Dies ist der Code, den ich benutze:

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

Wie Sie sehen können, ist es sehr einfach . Deshalb bin ich sehr verwirrt über das seltsame Verhalten, das ich bekomme, da diese Standardimplementierung keine Fehler verursachen sollte, oder?

Ausgabe bekomme ich

Obwohl die Warnung durch Aufheben der Einschränkungen auf allen Bildschirmgrößen korrekt angezeigt wird, wäre ich für jede Hilfe, die ich bekomme, sehr dankbar.

linus_hologram
quelle
Konstruieren Sie andere Einschränkungen im Code?
Sh_Khan
@Sh_Khan nein ich nicht. Ich meine, natürlich habe ich andere Ansichten unter der Warnungsansicht, aber sie funktionieren einwandfrei und erzeugen keine Einschränkungsfehler. Aber ich habe nichts an den AlertView-Einschränkungen
geändert
1
@linus_hologram, verursacht das Ding einen visuellen Fehler auf dem Bildschirm? wenn ja dann was? Wenn nein , verschwenden Sie Ihre Zeit nicht damit, etwas zu reparieren, das nicht einmal kaputt ist.
Holex
1
Constraints Log ist nicht immer wahr, manchmal führt es zu irreführenden Dingen
Sh_Khan
1
Ich habe das Problem mit dem Debugger untersucht, es ist eine dynamisch hinzugefügte Einschränkung ... Ich kann diese Einschränkung nicht finden, bevor ich einen UIAlertController präsentiere ... Weitere Informationen finden Sie in meiner Antwort unten
Agisight

Antworten:

42

Dieser Fehler ist nicht kritisch, scheint ein nicht behobener Fehler von Apple zu sein. Diese Einschränkung wird unmittelbar nach der Präsentation im Animationsstil angezeigt. Geben Sie hier die Bildbeschreibung einIch habe versucht, es zu erfassen und zu ändern (Werte, Beziehungen, Priorität ändern), bevor ich es präsentiere - kein Erfolg aufgrund dieser dynamisch hinzugefügten Einschränkungen.

Wenn Sie die Animation ausschalten self.present(alert, animated: false)und verwenden alert.view.addSubview(UIView()), verschwindet der Fehler. Ich kann es nicht erklären, aber es funktioniert!

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)
Agisight
quelle
1
Wie hängt diese Antwort mit dem Einschränkungsproblem des OP zusammen?
Holex
3
schalte die Animation aus + alert.view.addSubview(UIView())hat bei mir funktioniert. Aber ohne die Animation fühlt es sich so komisch an ... Trotzdem danke für diese Lösung.
Tieda Wei
1
Ja, ich erkläre es als Fehler von Apple. Ich habe es erklärt. Wenn Sie jedoch eine Lösung finden können, können Sie versuchen, die erforderlichen Einschränkungen zu ermitteln und zu ändern. Ich denke, das ist keine gute Idee, denn es ist ein Fehler.
Agisight
1
Ich denke, dass das Hinzufügen einer Unteransicht die Neuberechnung der Autolayout-Einschränkungen erzwingt und die Warnung stummschaltet.
FormigaNinja
3
Dieses Problem tritt auf, wenn animationtrue festgelegt ist. Ich denke, es ist ein interner Fehler und er kann ignoriert werden, da das actoinSheet richtig angezeigt wird. Ich bin froh, dass ich es auf die Grundursache eingrenzen konnte. Zuerst habe ich gelernt, dass es mein Code ist.
Jerry Okafor
44

Im Folgenden wird die Warnung entfernt, ohne dass die Animation deaktiviert werden muss. Und wenn Apple schließlich die Grundursache der Warnung behebt, sollte nichts anderes kaputt gehen.

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

Dies kann dann folgendermaßen verwendet werden:

// After all addActions(...), just before calling present(...)
alertController.pruneNegativeWidthConstraints()
jims1103
quelle
1
Funktioniert super, keine Holzverschmutzung mehr! 🥳
Klaas
Dies verursacht jetzt tatsächlich mehr Probleme (iOS 13.2.2), nicht sicher, wie es bei früheren Versionen funktioniert hat. UIAlertController wird am oberen Bildschirmrand angezeigt.
Jovanjovanovic
@jovanjovanovic Es tut mir leid zu hören, dass Sie das erleben, aber ich kann nicht reproduzieren, was Sie beschreiben. Diese Lösung ist immer noch diejenige, die die Warnungen ohne nachteilige Auswirkungen entfernt. Wenn Sie ein Codebeispiel haben, das das reproduzieren kann, was Sie beschreiben, teilen Sie es bitte mit.
jims1103
Ich benutze diese Cose direkt nach dem Erstellen des Alerts, aber es scheint nicht zu funktionieren! Wo kann man den Code am besten ablegen?
Lucas Tegliabue
@LucasTegliabue verwenden Sie es, bevor Sie alertcontroller wie let alertController = UIAlertController () alertController.pruneNegativeWidthConstraints () self.present (alertController, animiert: true, Vervollständigung: null)
Jack
15

Es ist ein neuer Fehler in iOS-Versionen:

  • 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

Das einzige, was wir tun können, ist, einen Fehlerbericht bei Apple einzureichen (das habe ich gerade getan, und Sie sollten es auch tun).

Ich werde versuchen, die Antwort für eine neue Version von iOS zu aktualisieren, wenn sie herauskommt.

Andrei Konstantinov
quelle
8

Hinzufügen zu dieser Antwort ... Dies scheint das Problem für mich zu beseitigen und erfordert keine Änderungen am vorhandenen Code.

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)
            }
        }
    }
}
Joe Schofield
quelle
2

Sichere Lösung

Sie sollten die Einschränkung nicht entfernen, da sie in Zukunft mit einem korrekten Wert verwendet wird.

Alternativ können Sie die Konstante in einen positiven Wert ändern:

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

Und dann, um Ihren Controller zu initialisieren, verwenden Sie:

let controller = PXAlertController(title: "Title", message: "Message", preferredStyle: .actionSheet)
Josh Bernfeld
quelle
Ich habe alle in Swift vorgeschlagenen Lösungsvorschläge ohne Erfolg ausprobiert. Ich verwende XCode 11.5, kompiliere für ios 13.0 und laufe auf dem iPhone Xs Max. Immer noch bekommen "<NSLayoutConstraint:0x28002b930 UIView:0x102e32420.width == - 16 (active)>". Ich kann nicht viel helfen, wenn ich Ihnen nicht sage, dass das Problem immer noch besteht. Wie jemand vorgeschlagen hat, werde ich den Fehler einfach ignorieren, aber ich mag es nicht.
Nicola Mingotti
es funktioniert überhaupt nicht. Die Lösung von RichW funktioniert mindestens unter iOS 13
Vyachaslav Gerchicov
@VyachaslavGerchicov Seien Sie jedoch vorsichtig, ich glaube, dass ältere iOS-Versionen abstürzen würden, wenn eine erforderliche Einschränkung zur Laufzeit in nicht erforderlich geändert wird. stackoverflow.com/questions/31186187/…
Josh Bernfeld
1

Eine alternative Möglichkeit, dem NSLayoutConstraint-Fehler zu entkommen, ist die Verwendung von preferredStyle: .alertanstelle von preferredStyle: .actionSheet. Dies funktioniert ohne Generierung von Warnungen, zeigt das Menü jedoch modal an.

user2430797
quelle
0

Die Lösung für Objective-C:

  1. Unterklassifizieren Sie Ihren eigenen Alert Controller von UIAlertController
  2. Definieren Sie die Bereinigungsfunktion wie in der vorherigen Antwort

    @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
    
Alexandr Kirilov
quelle
0

Interessante Ideen hier. Persönlich mag ich die Idee nicht, die Einschränkung zu löschen oder ihren Wert (Größe) zu ändern.

Da das Problem davon abhängt, dass die Beschränkungsauflösung in eine Position gezwungen wird, in der eine vorgeschriebene Beschränkung (Priorität 1000) aufgehoben werden muss, besteht ein weniger brutaler Ansatz darin, dem Rahmen lediglich mitzuteilen, dass diese Beschränkung bei Bedarf aufgehoben werden könnte.

Also (basierend auf Joshs "Safe" -Klasse):

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)
                }
            }
        }
    }
}

Dies hat den Vorteil, dass es keine Layoutabmessungen ändert, und es besteht auch eine gute Chance, dass es sich im Falle einer Korrektur im Framework gut verhält.

Getestet im iPhone SE-Simulator (der mir mein ursprüngliches Problem bereitete) - das Debugging im Zusammenhang mit Einschränkungen ist weg.

RichW
quelle
Funktioniert unter iOS 13, aber nicht unter iOS 12. Versucht auf dem iPhone X-Simulator
Vyachaslav Gerchicov
@RichW das ist ein guter Ansatz. Seien Sie jedoch vorsichtig, ich glaube, dass ältere iOS-Versionen abstürzen würden, wenn eine erforderliche Einschränkung zur Laufzeit in nicht erforderlich geändert würde. stackoverflow.com/questions/31186187/…
Josh Bernfeld
0

Wenn Sie die Animation und alle Einschränkungen beibehalten möchten, sollten Sie eine negative Einschränkung finden und positiv machen, bevor Sie den Alert Controller präsentieren.

// 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)
Denis Bystruev
quelle