Posso modificare la proprietà del moltiplicatore per NSLayoutConstraint?


143

Ho creato due viste in una superview e quindi ho aggiunto vincoli tra le viste:

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

Ora voglio cambiare la proprietà del moltiplicatore con l'animazione, ma non riesco a capire come cambiare la proprietà multipler. (Ho trovato _coefficiente nella proprietà privata nel file di intestazione NSLayoutConstraint.h, ma è privato.)

Come posso modificare la proprietà multipler?

La mia soluzione è rimuovere il vecchio vincolo e aggiungere quello nuovo con un valore diverso per multipler.


1
Il tuo attuale approccio per rimuovere il vecchio vincolo e aggiungerne uno nuovo è la scelta giusta. Capisco che non sembra "giusto", ma è il modo in cui dovresti farlo.
Rob,

4
Penso che il moltiplicatore non dovrebbe essere costante. È un cattivo design.
Borzh,

1
@Borzh perché attraverso i moltiplicatori faccio vincoli adattativi. Per iOS ridimensionabili.
Bimawa,

1
Sì, voglio anche cambiare il moltiplicatore in modo che le viste possano mantenerne il rapporto con la vista principale (non sia larghezza / altezza ma solo larghezza o altezza), ma la cosa strana che Apple non lo consente. Voglio dire, è il cattivo design di Apple, non il tuo.
Borzh,

Questa è una bella chiacchierata e copre questo e altro youtube.com/watch?v=N5Ert6LTruY
DogCoffee

Risposte:


117

Se hai solo due set di moltiplicatori che devono essere applicati, da iOS8 in poi puoi aggiungere entrambi i set di vincoli e decidere quali dovrebbero essere attivi in ​​qualsiasi momento:

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

Versione 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 Sì, puoi :)
Ja͢ck,

7
Se hai bisogno di più di 2 serie di vincoli, puoi aggiungerne quanti ne desideri e modificarne le proprietà prioritarie. In questo modo, per 2 vincoli non è necessario impostare uno su attivo e l'altro su inattivo, basta impostare la priorità su più alta o più bassa. Nell'esempio sopra, imposta la priorità di StandardConstraint su, diciamo 997, e quando vuoi che sia attiva basta impostare la priorità di ZoomedConstraint su 995, altrimenti impostala su 999. Assicurati anche che XIB non abbia le priorità impostate su 1000, altrimenti non sarai in grado di cambiarli e otterrai un incidente fantastico.
Cane

1
@WonderDog ha qualche vantaggio particolare, però? il linguaggio di abilitazione e disabilitazione mi sembra più facile da digerire che dover elaborare priorità relative.
Ja͢ck,

2
@WonderDog / Jack Ho finito per combinare i tuoi approcci perché il mio vincolo era stato definito nello storyboard. E se ho creato due contrapposizioni, entrambe con la stessa priorità ma moltiplicatori diversi, sono in conflitto e mi danno molto rosso. E da quello che posso dire non puoi impostarne uno come inattivo tramite IB. Quindi ho creato il mio vincolo principale con la priorità 1000 e il mio vincolo secondario che voglio animare con la priorità 999 (suona bene in IB). Quindi, all'interno di un blocco di animazione, ho impostato la proprietà attiva della persona primaria su false e questa è stata animata piacevolmente su quella secondaria. Grazie ad entrambi per l'aiuto!
Clay Garrett,

2
Tieni presente che probabilmente desideri forti riferimenti ai tuoi vincoli se li attivi e li disattivi, poiché "Attivazione o disattivazione delle chiamate ai vincoli addConstraint(_:)e removeConstraint(_:)nella vista che è l'antenato comune più vicino agli elementi gestiti da questo vincolo".
qix

166

Ecco un'estensione NSLayoutConstraint in Swift che semplifica l'impostazione di un nuovo moltiplicatore:

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

Uso della demo:

@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])deve essere eseguito prima della creazione newConstraint, altrimenti potrebbe causare un avviso: impossibile soddisfare contemporaneamente i vincoli . Inoltre newConstraint.active = truenon è necessario poiché NSLayoutConstraint.activateConstraints([newConstraint])fa esattamente la stessa cosa.
tzaloga,

1
attivare e disattivare non funziona prima di iOS8, c'è qualche alternativa per quello ??
narahari_arjun,

2
Questo non funziona quando si cambia di nuovo il moltiplicatore, ottiene un valore nullo
J. Doe,

4
Grazie per averci risparmiato tempo! Rinominerei la funzione in qualcosa del genere cloneWithMultiplierper renderlo più esplicito che non si applica solo agli effetti collaterali ma piuttosto restituisce un nuovo vincolo
Alexandre G

5
Notare che se si imposta il moltiplicatore su 0.0non sarà possibile aggiornarlo di nuovo.
Daniel Storm,

58

La multiplierproprietà è di sola lettura. È necessario rimuovere il vecchio NSLayoutConstraint e sostituirlo con uno nuovo per modificarlo.

Tuttavia, poiché sai che vuoi cambiare il moltiplicatore, puoi semplicemente cambiare la costante moltiplicandola tu stesso quando sono necessarie modifiche che spesso sono meno codice.


64
Sembra strano che il moltiplicatore sia di sola lettura. Non me lo aspettavo!
jowie,

135
@jowie è ironico che il valore "costante" possa essere modificato mentre il moltiplicatore non può.
Tomas Andrle,

7
Questo è sbagliato. Un NSLayoutConstraint specifica un vincolo di uguaglianza affine (in). Ad esempio: "View1.bottomY = A * View2.bottomY + B" 'A' è il moltiplicatore, 'B' è la costante. Non importa come si sintonizza B, non produrrà la stessa equazione della modifica del valore di A.
Tamás Zahola,

8
@FruityGeek ok, quindi stai usando View2.bottomYil valore corrente per calcolare la nuova costante del vincolo . Questo è imperfetto, poiché View2.bottomYil vecchio valore verrà inserito nella costante, quindi devi rinunciare allo stile dichiarativo e aggiornare manualmente il vincolo in qualsiasi momento View2.bottomY. In questo caso è possibile eseguire anche il layout manuale.
Tamás Zahola,

2
Questo non funzionerà nel caso in cui voglio che NSLayoutConstraint risponda per la modifica dei limiti o la rotazione del dispositivo senza codice aggiuntivo.
tzaloga,

49

Una funzione di supporto che utilizzo per modificare il moltiplicatore di un vincolo di layout esistente. Crea e attiva un nuovo vincolo e disattiva quello vecchio.

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

Utilizzo, cambiando il moltiplicatore in 1.2:

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

1
È applicabile da iOS 8? oppure può essere utilizzato nelle versioni precedenti
Durai Amuthan.H

1
NSLayoutConstraint.deactivatela funzione è solo iOS 8.0+.
Evgenii,

Perché lo stai restituendo?
mskw,

@mskw, la funzione restituisce il nuovo oggetto vincolo che crea. Il precedente può essere scartato.
Evgenii,

42

Versione Objective-C per la risposta di Andrew Schreiber

Creare la categoria per la classe NSLayoutConstraint e aggiungere il metodo nel file .h in questo modo

    #import <UIKit/UIKit.h>

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

Nel file .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

Successivamente in ViewController creare l'outlet per il vincolo che si desidera aggiornare.

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

e aggiorna il moltiplicatore dove vuoi come di seguito ..

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

Ho spostato la disattivazione in questo codice di esempio in quanto active = trueè uguale a activare e pertanto causa l'aggiunta di un vincolo duplicato prima che venga richiamata la disattivazione, ciò provoca un errore di vincoli in alcuni scenari.
Jules il

13

Puoi invece modificare la proprietà "costante" per raggiungere lo stesso obiettivo con un po 'di matematica. Supponiamo che il moltiplicatore predefinito sul vincolo sia 1.0f. Questo è il codice C # di Xamarin che può essere facilmente tradotto in obiettivo-c

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

questa è una bella soluzione
J. Doe,

3
Questo si interromperà se il frame di secondItem cambia larghezza dopo l'esecuzione del codice sopra. Va bene se secondItem non cambierà mai dimensione, ma se secondItem cambierà dimensione, il vincolo non calcolerà più il valore corretto per il layout.
John Stephen,

Come sottolineato da John Stephen, questo non funzionerà per viste dinamiche, dispositivi: non si aggiorna automaticamente insieme al layout.
Womble,

1
Ottima soluzione per il mio caso, in cui la larghezza del secondo elemento è in realtà [UIScreen mainScreen] .bounds.size.width (che non cambia mai)
Arik Segal

7

Come è spiegato in altre risposte: è necessario rimuovere il vincolo e crearne uno nuovo.


È possibile evitare di restituire un nuovo vincolo creando un metodo statico NSLayoutConstraintcon inoutparametro, che consente di riassegnare il vincolo passato

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
    }

}

Esempio di utilizzo:

@IBOutlet weak var constraint: NSLayoutConstraint!

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

2

NESSUNO DEL CODICE SOPRA LAVORATO PER ME COSÌ DOPO TENTANDO DI MODIFICARE IL MIO CODICE PROPRIO QUESTO CODICE Questo codice funziona in Xcode 10 e 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() 
 }

grazie, sembra funzionare bene
Radu Ursache il

benvenuta radu-ursache
Ullas Pujary il

2

Sì, possiamo modificare i valori del moltiplicatore, basta fare un'estensione di NSLayoutConstraint e usarlo come ->

   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

Passa modificando il vincolo attivo nel codice come suggerito da molte altre risposte non ha funzionato per me. Quindi ho creato 2 vincoli, uno installato e l'altro no, associo entrambi al codice, quindi passa rimuovendo uno e aggiungendo l'altro.

Per completezza, per vincolare il vincolo trascinare il vincolo sul codice usando il tasto destro del mouse, proprio come qualsiasi altro elemento grafico:

inserisci qui la descrizione dell'immagine

Ho chiamato una proporzione IPad e l'altra proporzione iPhone.

Quindi, aggiungi il seguente codice, in viewDidLoad

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

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

Sto usando xCode 10 e swift 5.0


0

Ecco una risposta basata sulla risposta di @ Tianfu in C #. Altre risposte che richiedono l'attivazione e la disattivazione dei vincoli non hanno funzionato per me.

    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

Per modificare il valore del moltiplicatore, attenersi alla seguente procedura: 1. creare punti vendita per il vincolo richiesto, ad es. SomeConstraint. 2. modifica il valore del moltiplicatore come someConstraint.constant * = 0.8 o qualsiasi valore del moltiplicatore.

tutto qui, buona programmazione.


-1

Si può leggere:

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

in questa pagina di documentazione . Ciò non significa che si dovrebbe essere in grado di modificare il moltiplicatore (dal momento che è un var)?


var multiplier: CGFloat { get }è la definizione che significa che ha solo un getter.
Jonny,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.