Puis-je modifier la propriété du multiplicateur pour NSLayoutConstraint?


143

J'ai créé deux vues dans un superview, puis j'ai ajouté des contraintes entre les vues:

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

Maintenant, je veux changer la propriété du multiplicateur avec une animation, mais je ne peux pas comprendre comment changer la propriété du multiplicateur. (J'ai trouvé _coefficient dans la propriété privée dans le fichier d'en-tête NSLayoutConstraint.h, mais il est privé.)

Comment changer la propriété du multiplicateur?

Ma solution de contournement consiste à supprimer l'ancienne contrainte et à ajouter la nouvelle avec une valeur différente pour multipler.


1
Votre approche actuelle consistant à supprimer l'ancienne contrainte et à en ajouter une nouvelle est le bon choix. Je comprends que cela ne semble pas «bien», mais c'est la façon dont vous êtes censé le faire.
Rob

4
Je pense que le multiplicateur ne devrait pas être constant. C'est une mauvaise conception.
Borzh

1
@Borzh pourquoi à travers des multiplicateurs je crée des contraintes adaptatives. Pour redimensionner iOS.
Bimawa

1
Oui, je veux aussi changer le multiplicateur pour que les vues puissent garder son rapport à la vue parent (pas à la fois largeur / hauteur mais juste largeur ou hauteur), mais la chose étrange qu'Apple ne permet pas. Je veux dire que c'est la mauvaise conception d'Apple, pas la vôtre.
Borzh

Ceci est une excellente conférence et couvre ceci et plus encore youtube.com/watch?v=N5Ert6LTruY
DogCoffee

Réponses:


117

Si vous n'avez que deux ensembles de multiplicateurs qui doivent être appliqués, à partir d' iOS8 , vous pouvez ajouter les deux ensembles de contraintes et décider lequel doit être actif à tout moment:

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 ...]

Version Swift 5.0

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

2
@micnguyen Oui, vous pouvez :)
Ja͢ck

7
Si vous avez besoin de plus de 2 ensembles de contraintes, vous pouvez en ajouter autant que vous le souhaitez et modifier leurs propriétés de priorité. De cette façon, pour 2 contraintes, vous n'avez pas à définir l'une sur active et l'autre comme inactive, vous définissez simplement la priorité supérieure ou inférieure. Dans l'exemple ci-dessus, définissez la priorité standardConstraint sur, disons 997, et lorsque vous voulez qu'elle soit active, définissez la priorité de zoomedConstraint sur 995, sinon définissez-la sur 999. Assurez-vous également que le XIB n'a pas les priorités définies sur 1000, sinon vous ne pourrez pas les changer et vous aurez un crash impressionnant.
Chien

1
@WonderDog est-ce que cela présente un avantage particulier? l'idiome d'activation et de désactivation me semble plus facile à digérer que d'avoir à définir des priorités relatives.
Ja͢ck

2
@WonderDog / Jack J'ai fini par combiner vos approches car ma contrainte était définie dans le storyboard. Et si je créais deux contraintes, toutes deux avec la même priorité mais des multiplicateurs différents, elles étaient en conflit et me donnaient beaucoup de rouge. Et d'après ce que je peux dire, vous ne pouvez pas en définir un comme inactif via IB. J'ai donc créé ma contrainte principale avec la priorité 1000 et ma contrainte secondaire que je veux animer avec la priorité 999 (joue bien dans IB). Ensuite, dans un bloc d'animation, j'ai défini la propriété active du primaire sur false et elle s'est bien animée sur la propriété secondaire. Merci à tous les deux pour l'aide!
Clay Garrett

2
Gardez à l'esprit que vous souhaiterez probablement des références fortes à vos contraintes si vous les activez et les désactivez, car "L'activation ou la désactivation des contraintes appelle addConstraint(_:)et removeConstraint(_:)sur la vue qui est l'ancêtre commun le plus proche des éléments gérés par cette contrainte".
qix

166

Voici une extension NSLayoutConstraint dans Swift qui facilite la définition d'un nouveau multiplicateur:

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

Utilisation de la démonstration:

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

11
NSLayoutConstraint.deactivateConstraints([self])doit être fait avant la création newConstraint, sinon cela peut entraîner un avertissement: Impossible de satisfaire simultanément les contraintes . Est également newConstraint.active = trueinutile puisque NSLayoutConstraint.activateConstraints([newConstraint])fait exactement la même chose.
tzaloga

1
activer et désactiver ne fonctionne pas avant ios8, y a-t-il une alternative pour cela ??
narahari_arjun

2
Cela ne fonctionne pas lors du changement de multiplicateur à nouveau, il obtient une valeur nulle
J. Doe

4
Merci de nous faire gagner du temps! Je renommerais la fonction en quelque chose comme cloneWithMultiplierpour rendre plus explicite qu'elle n'applique pas seulement des effets secondaires, mais renvoie plutôt une nouvelle contrainte
Alexandre G

5
Notez que si vous définissez le multiplicateur sur, 0.0vous ne pourrez plus le mettre à jour.
Daniel Storm

58

le multiplier propriété est en lecture seule. Vous devez supprimer l'ancien NSLayoutConstraint et le remplacer par un nouveau pour le modifier.

Cependant, puisque vous savez que vous voulez changer le multiplicateur, vous pouvez simplement changer la constante en la multipliant vous-même lorsque des modifications sont nécessaires, ce qui est souvent moins de code.


64
Cela semble étrange que le multiplicateur soit en lecture seule. Je ne m'attendais pas à ça!
jowie

135
@jowie il est ironique que la valeur "constante" puisse être modifiée alors que le multiplicateur ne le peut pas.
Tomas Andrle

7
C'est faux. Un NSLayoutConstraint spécifie une contrainte d'égalité affine (in). Par exemple: "View1.bottomY = A * View2.bottomY + B" 'A' est le multiplicateur, 'B' est la constante. Peu importe comment vous réglez B, cela ne produira pas la même équation que la modification de la valeur de A.
Tamás Zahola

8
@FruityGeek ok, donc vous utilisez View2.bottomYla valeur actuelle de la contrainte pour calculer la nouvelle constante de la contrainte . C'est imparfait, puisque View2.bottomYl'ancienne valeur de la constante sera intégrée à la constante, vous devez donc abandonner le style déclaratif et mettre à jour manuellement la contrainte à tout moment View2.bottomY. Dans ce cas, vous pouvez également effectuer une mise en page manuelle.
Tamás Zahola

2
Cela ne fonctionnera pas au cas où je voudrais que NSLayoutConstraint réponde au changement de limites ou à la rotation de l'appareil sans code supplémentaire.
tzaloga

49

Une fonction d'aide que j'utilise pour changer le multiplicateur d'une contrainte de disposition existante. Il crée et active une nouvelle contrainte et désactive l'ancienne.

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

Utilisation, changement du multiplicateur à 1,2:

constraint = MyConstraint.changeMultiplier(constraint, multiplier: 1.2)

1
Est-il applicable à partir d'iOS 8? ou il peut être utilisé dans les versions antérieures
Durai Amuthan.H

1
NSLayoutConstraint.deactivateLa fonction est iOS 8.0+ uniquement.
Evgenii

Pourquoi le renvoyez-vous?
mskw

@mskw, la fonction renvoie le nouvel objet de contrainte qu'elle crée. Le précédent peut être rejeté.
Evgenii

42

Version Objective-C pour la réponse d'Andrew Schreiber

Créez la catégorie pour la classe NSLayoutConstraint et ajoutez la méthode dans le fichier .h comme ceci

    #import <UIKit/UIKit.h>

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

Dans le fichier .m

#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

Plus tard dans le ViewController, créez la prise pour la contrainte que vous souhaitez mettre à jour.

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

et mettez à jour le multiplicateur où vous le souhaitez, comme ci-dessous.

self.topConstraint = [self.topConstraint updateMultiplier:0.9099];

J'ai déplacé désactiver dans cet exemple de code de active = truela même manière que activate et provoque donc l'ajout d'une contrainte en double avant que la désactivation ne soit appelée, cela provoque une erreur de contraintes dans certains scénarios.
Jules

13

Vous pouvez modifier la propriété "constante" à la place pour atteindre le même objectif avec un peu de maths. Supposons que votre multiplicateur par défaut sur la contrainte est 1.0f. Il s'agit de code Xamarin C # qui peut être facilement traduit en objective-c

private void SetMultiplier(nfloat multiplier)
{
    FirstItemWidthConstraint.Constant = -secondItem.Frame.Width * (1.0f - multiplier);
}

c'est une bonne solution
J. Doe

3
Cela se cassera si le cadre de secondItem change de largeur après l'exécution du code ci-dessus. Ce n'est pas grave si secondItem ne changera jamais de taille, mais si secondItem change de taille, la contrainte ne calculera plus la valeur correcte pour la mise en page.
John Stephen

Comme l'a souligné John Stephen, cela ne fonctionnera pas pour les vues dynamiques, les appareils: il ne se met pas automatiquement à jour avec la mise en page.
Womble

1
Excellente solution pour mon cas, dans lequel la largeur du deuxième élément est en fait [UIScreen mainScreen] .bounds.size.width (qui ne change jamais)
Arik Segal

7

Comme dans d'autres réponses expliquées: vous devez supprimer la contrainte et en créer une nouvelle.


Vous pouvez éviter de renvoyer une nouvelle contrainte en créant une méthode statique pour NSLayoutConstraintwith inoutparameter, qui vous permet de réaffecter la contrainte passée

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
    }

}

Exemple d'utilisation:

@IBOutlet weak var constraint: NSLayoutConstraint!

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

2

AUCUN DES CODE CI-DESSUS N'A FONCTIONNÉ POUR MOI APRÈS AVOIR TENTÉ DE MODIFIER MON PROPRE CODE CE CODE Ce code fonctionne dans Xcode 10 et swift 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() 
 }

merci, semble bien fonctionner
Radu Ursache

welcome radu-ursache
Ullas Pujary

2

Oui, nous pouvons changer les valeurs de multiplicateur, il suffit de créer une extension de NSLayoutConstraint et de l'utiliser comme ->

   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)

1

Changer en changeant la contrainte active dans le code comme suggéré par de nombreuses autres réponses n'a pas fonctionné pour moi. J'ai donc créé 2 contraintes, l'une installée et l'autre non, lier les deux au code, puis basculer en supprimant l'une et en ajoutant l'autre.

Par souci d'exhaustivité, pour lier la contrainte, faites glisser la contrainte vers le code en utilisant le bouton droit de la souris, comme tout autre élément graphique:

entrez la description de l'image ici

J'ai nommé une proportionIPad et l'autre proportionIPhone.

Ensuite, ajoutez le code suivant, à viewDidLoad

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

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

J'utilise xCode 10 et Swift 5.0


0

Voici une réponse basée sur la réponse de @ Tianfu en C #. D'autres réponses qui nécessitent l'activation et la désactivation des contraintes n'ont pas fonctionné pour moi.

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

0

Pour modifier la valeur du multiplicateur, suivez les étapes ci-dessous: 1. créez des points de vente pour la contrainte requise, par exemple someConstraint. 2. modifiez la valeur du multiplicateur comme someConstraint.constant * = 0.8 ou n'importe quelle valeur de multiplicateur.

c'est tout, codage heureux.


-1

On peut lire:

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

sur cette page de documentation . Cela ne veut-il pas dire qu'il faut pouvoir modifier le multiplicateur (puisqu'il s'agit d'un var)?


var multiplier: CGFloat { get }est la définition qui signifie qu'il n'a qu'un getter.
Jonny
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.