Kann ich die Multiplikatoreigenschaft für NSLayoutConstraint ändern?

141

Ich habe zwei Ansichten in einer Übersicht erstellt und dann Einschränkungen zwischen den Ansichten hinzugefügt:

_indicatorConstrainWidth = [NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view2 attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0.0f];
[_indicatorConstrainWidth setPriority:UILayoutPriorityDefaultLow];
_indicatorConstrainHeight = [NSLayoutConstraint constraintWithItem:self.view1 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.view2 attribute:NSLayoutAttributeHeight multiplier:1.0f constant:0.0f];
[_indicatorConstrainHeight setPriority:UILayoutPriorityDefaultLow];
[self addConstraint:_indicatorConstrainWidth];
[self addConstraint:_indicatorConstrainHeight];

Jetzt möchte ich die Multiplikatoreigenschaft mit Animation ändern, aber ich kann nicht herausfinden, wie die Multiplikatoreigenschaft geändert werden kann. (Ich fand _koeffizient in Privateigentum in der Header-Datei NSLayoutConstraint.h, aber es ist privat.)

Wie ändere ich die Multiplikatoreigenschaft?

Meine Problemumgehung besteht darin, die alte Einschränkung zu entfernen und die neue mit einem anderen Wert für hinzuzufügen multipler .

Bimawa
quelle
1
Ihr aktueller Ansatz, alte Einschränkungen zu entfernen und neue hinzuzufügen, ist die richtige Wahl. Ich verstehe, dass es sich nicht "richtig" anfühlt, aber so soll man es machen.
Rob
4
Ich denke, der Multiplikator sollte nicht konstant sein. Es ist ein schlechtes Design.
Borzh
1
@ Borzh warum mache ich durch Multiplikatoren adaptive Einschränkungen. Für Größenänderungen.
Bimawa
1
Ja, ich möchte auch den Multiplikator ändern, damit Ansichten das Verhältnis zur übergeordneten Ansicht beibehalten können (nicht sowohl Breite / Höhe als auch Breite oder Höhe), aber das Seltsame, das Apple nicht zulässt. Ich meine, es ist Apples schlechtes Design, nicht deins.
Borzh
Dies ist ein großartiger Vortrag und behandelt dies und mehr youtube.com/watch?v=N5Ert6LTruY
DogCoffee

Antworten:

117

Wenn Sie nur zwei Sätze von Multiplikatoren haben, die angewendet werden müssen, können Sie ab iOS8 beide Sätze von Einschränkungen hinzufügen und entscheiden, welche jederzeit aktiv sein sollen:

NSLayoutConstraint *standardConstraint, *zoomedConstraint;

// ...
// switch between constraints
standardConstraint.active = NO; // this line should always be the first line. because you have to deactivate one before activating the other one. or they will conflict.
zoomedConstraint.active = YES;
[self.view layoutIfNeeded]; // or using [UIView animate ...]

Swift 5.0 Version

var standardConstraint: NSLayoutConstraint!
var zoomedConstraint: NSLayoutConstraint!

// ...

// switch between constraints
standardConstraint.isActive = false // this line should always be the first line. because you have to deactivate one before activating the other one. or they will conflict.
zoomedConstraint.isActive = true
self.view.layoutIfNeeded() // or using UIView.animate
Jack
quelle
2
@micnguyen Ja, Sie können :)
Ja͢ck
7
Wenn Sie mehr als zwei Sätze von Einschränkungen benötigen, können Sie beliebig viele hinzufügen und deren Prioritätseigenschaften ändern. Auf diese Weise müssen Sie für zwei Einschränkungen nicht eine auf aktiv und die andere auf inaktiv setzen, sondern nur die Priorität höher oder niedriger einstellen. Setzen Sie im obigen Beispiel die standardConstraint-Priorität auf beispielsweise 997, und wenn Sie möchten, dass sie aktiv ist, setzen Sie die Priorität von zoomedConstraint einfach auf 995, andernfalls auf 999. Stellen Sie außerdem sicher, dass für XIB die Prioritäten nicht auf 1000 festgelegt sind Sie werden sie nicht ändern können und Sie werden einen fantastischen Absturz bekommen.
Hund
1
@WonderDog hat das aber einen besonderen Vorteil? Die Sprache des Aktivierens und Deaktivierens scheint mir leichter zu verdauen zu sein, als relative Prioritäten herausarbeiten zu müssen.
Ja͢ck
2
@WonderDog / Jack Ich habe Ihre Ansätze kombiniert, weil meine Einschränkung im Storyboard definiert wurde. Und wenn ich zwei Beschränkungen erstellt habe, beide mit der gleichen Priorität, aber unterschiedlichen Multiplikatoren, sind sie in Konflikt geraten und haben mir viel Rot gegeben. Und soweit ich das beurteilen kann, können Sie über IB keine als inaktiv festlegen. Also habe ich meine Haupteinschränkung mit Priorität 1000 und meine Nebenbedingung erstellt, zu der ich mit Priorität 999 animieren möchte (spielt sich in IB gut). Dann habe ich in einem Animationsblock die aktive Eigenschaft des primären auf false gesetzt und sie gut auf den sekundären animiert. Vielen Dank für die Hilfe!
Clay Garrett
2
Beachten Sie, dass Sie beim Aktivieren und Deaktivieren dieser Einschränkungen wahrscheinlich starke Verweise auf Ihre Einschränkungen wünschen, da "Aktivieren oder Deaktivieren der Einschränkungsaufrufe addConstraint(_:)und removeConstraint(_:)in der Ansicht, die der nächste gemeinsame Vorfahr der von dieser Einschränkung verwalteten Elemente ist".
Qix
166

Hier ist eine NSLayoutConstraint-Erweiterung in Swift, die das Festlegen eines neuen Multiplikators ziemlich einfach macht:

In Swift 3.0+

import UIKit
extension NSLayoutConstraint {
    /**
     Change multiplier constraint

     - parameter multiplier: CGFloat
     - returns: NSLayoutConstraint
    */
    func setMultiplier(multiplier:CGFloat) -> NSLayoutConstraint {

        NSLayoutConstraint.deactivate([self])

        let newConstraint = NSLayoutConstraint(
            item: firstItem,
            attribute: firstAttribute,
            relatedBy: relation,
            toItem: secondItem,
            attribute: secondAttribute,
            multiplier: multiplier,
            constant: constant)

        newConstraint.priority = priority
        newConstraint.shouldBeArchived = self.shouldBeArchived
        newConstraint.identifier = self.identifier

        NSLayoutConstraint.activate([newConstraint])
        return newConstraint
    }
}

Demo-Nutzung:

@IBOutlet weak var myDemoConstraint:NSLayoutConstraint!

override func viewDidLoad() {
    let newMultiplier:CGFloat = 0.80
    myDemoConstraint = myDemoConstraint.setMultiplier(newMultiplier)

    //If later in view lifecycle, you may need to call view.layoutIfNeeded() 
}
Andrew Schreiber
quelle
11
NSLayoutConstraint.deactivateConstraints([self])sollte vor dem Erstellen durchgeführt werden newConstraint, da dies sonst zu einer Warnung führen kann: Einschränkungen können nicht gleichzeitig erfüllt werden . Auch newConstraint.active = trueist unnötig, da NSLayoutConstraint.activateConstraints([newConstraint])genau das gleiche tut.
Tzaloga
1
aktivieren und deaktivieren funktioniert nicht vor ios8, gibt es dafür eine alternative ??
narahari_arjun
2
Dies funktioniert nicht, wenn der Multiplikator erneut
J. Doe
4
Vielen Dank, dass Sie uns Zeit gespart haben! Ich würde die Funktion in etwas umbenennen cloneWithMultiplier, um es deutlicher zu machen, dass sie nicht nur Nebenwirkungen anwendet, sondern eine neue Einschränkung zurückgibt
Alexandre G
5
Beachten Sie, dass Sie den Multiplikator 0.0nicht erneut aktualisieren können , wenn Sie ihn einstellen .
Daniel Storm
57

Die multiplierEigenschaft ist schreibgeschützt. Sie müssen die alte NSLayoutConstraint entfernen und durch eine neue ersetzen, um sie zu ändern.

Da Sie jedoch wissen, dass Sie den Multiplikator ändern möchten, können Sie die Konstante einfach ändern, indem Sie sie selbst multiplizieren, wenn Änderungen erforderlich sind, bei denen es sich häufig um weniger Code handelt.

Fruchtiger Geek
quelle
62
Scheint seltsam, dass der Multiplikator schreibgeschützt ist. Das habe ich nicht erwartet!
Jowie
134
@jowie Es ist ironisch, dass der "konstante" Wert geändert werden kann, während der Multiplikator dies nicht kann.
Tomas Andrle
7
Das ist falsch. Eine NSLayoutConstraint gibt eine affine (In-) Gleichheitsbedingung an. Zum Beispiel: "View1.bottomY = A * View2.bottomY + B" 'A' ist der Multiplikator, 'B' ist die Konstante. Unabhängig davon, wie Sie B einstellen, wird nicht die gleiche Gleichung wie beim Ändern des A-Werts erzeugt.
Tamás Zahola
8
@FruityGeek okay, Sie verwenden also View2.bottomYden aktuellen Wert, um die neue Konstante der Einschränkung zu berechnen . Das ist fehlerhaft, da View2.bottomYder alte Wert in der Konstante gebacken wird. Sie müssen also den deklarativen Stil aufgeben und die Einschränkung bei jeder View2.bottomYÄnderung manuell aktualisieren . In diesem Fall können Sie auch ein manuelles Layout erstellen.
Tamás Zahola
2
Dies funktioniert nicht, wenn NSLayoutConstraint ohne zusätzlichen Code auf Änderungen der Grenzen oder Gerätedrehung reagieren soll.
Tzaloga
49

Eine Hilfsfunktion, mit der ich den Multiplikator einer vorhandenen Layoutbeschränkung ändere . Es erstellt und aktiviert eine neue Einschränkung und deaktiviert die alte.

struct MyConstraint {
  static func changeMultiplier(_ constraint: NSLayoutConstraint, multiplier: CGFloat) -> NSLayoutConstraint {
    let newConstraint = NSLayoutConstraint(
      item: constraint.firstItem,
      attribute: constraint.firstAttribute,
      relatedBy: constraint.relation,
      toItem: constraint.secondItem,
      attribute: constraint.secondAttribute,
      multiplier: multiplier,
      constant: constraint.constant)

    newConstraint.priority = constraint.priority

    NSLayoutConstraint.deactivate([constraint])
    NSLayoutConstraint.activate([newConstraint])

    return newConstraint
  }
}

Verwendung, Änderung des Multiplikators auf 1,2:

constraint = MyConstraint.changeMultiplier(constraint, multiplier: 1.2)
Evgenii
quelle
1
Ist es ab iOS 8 anwendbar? oder es kann in früheren Versionen verwendet werden
Durai Amuthan.H
1
NSLayoutConstraint.deactivateFunktion ist nur iOS 8.0+.
Evgenii
Warum gibst du es zurück?
mskw
@mskw, die Funktion gibt das neue Einschränkungsobjekt zurück, das sie erstellt. Der vorherige kann verworfen werden.
Evgenii
42

Objective-C-Version für Andrew Schreiber Antwort

Erstellen Sie die Kategorie für die NSLayoutConstraint-Klasse und fügen Sie die Methode wie folgt in die .h-Datei ein

    #import <UIKit/UIKit.h>

@interface NSLayoutConstraint (Multiplier)
-(instancetype)updateMultiplier:(CGFloat)multiplier;
@end

In der .m-Datei

#import "NSLayoutConstraint+Multiplier.h"

@implementation NSLayoutConstraint (Multiplier)
-(instancetype)updateMultiplier:(CGFloat)multiplier {

    [NSLayoutConstraint deactivateConstraints:[NSArray arrayWithObjects:self, nil]];

   NSLayoutConstraint *newConstraint = [NSLayoutConstraint constraintWithItem:self.firstItem attribute:self.firstAttribute relatedBy:self.relation toItem:self.secondItem attribute:self.secondAttribute multiplier:multiplier constant:self.constant];
    [newConstraint setPriority:self.priority];
    newConstraint.shouldBeArchived = self.shouldBeArchived;
    newConstraint.identifier = self.identifier;
    newConstraint.active = true;

    [NSLayoutConstraint activateConstraints:[NSArray arrayWithObjects:newConstraint, nil]];
    //NSLayoutConstraint.activateConstraints([newConstraint])
    return newConstraint;
}
@end

Erstellen Sie später im ViewController den Ausgang für die Einschränkung, die Sie aktualisieren möchten.

@property (strong, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;

und aktualisieren Sie den Multiplikator, wo immer Sie möchten.

self.topConstraint = [self.topConstraint updateMultiplier:0.9099];
Koti Tummala
quelle
Ich habe Deaktivieren in diesem Beispielcode active = truewie Aktivieren verschoben und bewirkt daher, dass eine doppelte Einschränkung hinzugefügt wird, bevor Deaktivieren aufgerufen wird. Dies führt in einigen Szenarien zu einem Einschränkungsfehler.
Jules
13

Sie können stattdessen die Eigenschaft "Konstante" ändern, um dasselbe Ziel mit ein wenig Mathematik zu erreichen. Angenommen, Ihr Standardmultiplikator für die Einschränkung ist 1.0f. Dies ist Xamarin C # -Code, der leicht in Objective-C übersetzt werden kann

private void SetMultiplier(nfloat multiplier)
{
    FirstItemWidthConstraint.Constant = -secondItem.Frame.Width * (1.0f - multiplier);
}
Tianfu Dai
quelle
Dies ist eine schöne Lösung
J. Doe
3
Dies wird unterbrochen, wenn der Frame von secondItem die Breite ändert, nachdem der obige Code ausgeführt wurde. Es ist in Ordnung, wenn secondItem niemals die Größe ändert, aber wenn secondItem die Größe ändert, berechnet die Einschränkung nicht mehr den korrekten Wert für das Layout.
John Stephen
Wie von John Stephen hervorgehoben, funktioniert dies nicht für dynamische Ansichten und Geräte: Es wird nicht automatisch zusammen mit dem Layout aktualisiert.
Womble
1
Tolle Lösung für meinen Fall, in dem die Breite des zweiten Elements tatsächlich [UIScreen mainScreen] .bounds.size.width ist (das ändert sich nie)
Arik Segal
7

Wie in anderen Antworten erläutert: Sie müssen die Einschränkung entfernen und eine neue erstellen.


Sie können die Rückgabe einer neuen Einschränkung vermeiden, indem Sie eine statische Methode für NSLayoutConstraintmit inoutParameter erstellen, mit der Sie die übergebene Einschränkung neu zuweisen können

import UIKit

extension NSLayoutConstraint {

    static func setMultiplier(_ multiplier: CGFloat, of constraint: inout NSLayoutConstraint) {
        NSLayoutConstraint.deactivate([constraint])

        let newConstraint = NSLayoutConstraint(item: constraint.firstItem, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: constraint.secondItem, attribute: constraint.secondAttribute, multiplier: multiplier, constant: constraint.constant)

        newConstraint.priority = constraint.priority
        newConstraint.shouldBeArchived = constraint.shouldBeArchived
        newConstraint.identifier = constraint.identifier

        NSLayoutConstraint.activate([newConstraint])
        constraint = newConstraint
    }

}

Anwendungsbeispiel:

@IBOutlet weak var constraint: NSLayoutConstraint!

override func viewDidLoad() {
    NSLayoutConstraint.setMultiplier(0.8, of: &constraint)
    // view.layoutIfNeeded() 
}
Robert Dresler
quelle
2

Keiner der oben genannten Codes hat für mich funktioniert, nachdem versucht wurde, meinen eigenen Code zu ändern. Dieser Code funktioniert in Xcode 10 und schnell 4.2

import UIKit
extension NSLayoutConstraint {
/**
 Change multiplier constraint

 - parameter multiplier: CGFloat
 - returns: NSLayoutConstraintfor 
*/i
func setMultiplier(multiplier:CGFloat) -> NSLayoutConstraint {

    NSLayoutConstraint.deactivate([self])

    let newConstraint = NSLayoutConstraint(
        item: firstItem,
        attribute: firstAttribute,
        relatedBy: relation,
        toItem: secondItem,
        attribute: secondAttribute,
        multiplier: multiplier,
        constant: constant)

    newConstraint.priority = priority
    newConstraint.shouldBeArchived = self.shouldBeArchived
    newConstraint.identifier = self.identifier

    NSLayoutConstraint.activate([newConstraint])
    return newConstraint
}
}



 @IBOutlet weak var myDemoConstraint:NSLayoutConstraint!

override func viewDidLoad() {
let newMultiplier:CGFloat = 0.80
myDemoConstraint = myDemoConstraint.setMultiplier(newMultiplier)

//If later in view lifecycle, you may need to call view.layoutIfNeeded() 
 }
Ullas Pujary
quelle
danke, scheint gut zu funktionieren
Radu Ursache
Willkommen radu-ursache
Ullas Pujary vor
2

Ja, wir können Multiplikatorwerte ändern. Erstellen Sie einfach eine Erweiterung von NSLayoutConstraint und verwenden Sie sie wie ->

   func setMultiplier(_ multiplier:CGFloat) -> NSLayoutConstraint {

        NSLayoutConstraint.deactivate([self])

        let newConstraint = NSLayoutConstraint(
            item: firstItem!,
            attribute: firstAttribute,
            relatedBy: relation,
            toItem: secondItem,
            attribute: secondAttribute,
            multiplier: multiplier,
            constant: constant)

        newConstraint.priority = priority
        newConstraint.shouldBeArchived = shouldBeArchived
        newConstraint.identifier = identifier

        NSLayoutConstraint.activate([newConstraint])
        return newConstraint
    }

            self.mainImageViewHeightMultiplier = self.mainImageViewHeightMultiplier.setMultiplier(375.0/812.0)
Rajan Singh
quelle
1

Das Wechseln durch Ändern der aktiven Einschränkung im Code, wie in vielen anderen Antworten vorgeschlagen, hat bei mir nicht funktioniert. Also habe ich 2 Einschränkungen erstellt, eine installiert und die andere nicht, beide an den Code binden und dann wechseln, indem ich eine entferne und die andere hinzufüge.

Um der Vollständigkeit halber die Einschränkung zu binden, ziehen Sie die Einschränkung wie jedes andere grafische Element mit der rechten Maustaste in den Code:

Geben Sie hier die Bildbeschreibung ein

Ich habe ein Proportionen-iPad und das andere Proportionen-Telefon genannt.

Fügen Sie dann unter viewDidLoad den folgenden Code hinzu

override open func viewDidLoad() {
   super.viewDidLoad()
   if ... {

       view.removeConstraint(proportionIphone)
       view.addConstraint(proportionIpad) 
   }     
}

Ich benutze xCode 10 und Swift 5.0

MiguelSlv
quelle
0

Hier ist eine Antwort basierend auf der Antwort von @ Tianfu in C #. Andere Antworten, die das Aktivieren und Deaktivieren von Einschränkungen erfordern, haben bei mir nicht funktioniert.

    var isMapZoomed = false
@IBAction func didTapMapZoom(_ sender: UIButton) {
    let offset = -1.0*graphHeightConstraint.secondItem!.frame.height*(1.0 - graphHeightConstraint.multiplier)
    graphHeightConstraint.constant = (isMapZoomed) ? offset : 0.0

    isMapZoomed = !isMapZoomed
    self.view.layoutIfNeeded()
}
RawMean
quelle
0

Führen Sie die folgenden Schritte aus, um den Wert des Multiplikators zu ändern: 1. Erstellen Sie Ausgänge für die erforderliche Einschränkung, z. B. someConstraint. 2. Ändern Sie den Multiplikatorwert wie someConstraint.constant * = 0.8 oder einen beliebigen Multiplikatorwert.

das ist es, fröhliche Codierung.

Gulshan Kumar
quelle
-1

Man kann lesen:

var multiplier: CGFloat
The multiplier applied to the second attribute participating in the constraint.

auf dieser Dokumentationsseite . Bedeutet das nicht, dass man den Multiplikator ändern kann (da es sich um eine Var handelt)?

Michel
quelle
var multiplier: CGFloat { get }ist die Definition, was bedeutet, dass es nur einen Getter gibt.
Jonny