NSLayoutConstraint için çarpan özelliğini değiştirebilir miyim?


143

Bir denetimde iki görünüm oluşturdum ve ardından görünümler arasında kısıtlamalar ekledim:

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

Şimdi animasyonla çarpan özelliğini değiştirmek istiyorum, ancak çarpan özelliğini nasıl değiştireceğimi anlayamıyorum. (NSLayoutConstraint.h başlık dosyasında özel mülkte _coefficient buldum, ancak özel.)

Çarpan özelliğini nasıl değiştirebilirim?

Benim geçici çözüm eski kısıtlamayı kaldırmak ve yeni bir değer için farklı bir değer eklemek multipler.


1
Eski kısıtlamayı kaldırma ve yenisini ekleme konusundaki mevcut yaklaşımınız doğru seçimdir. Bunun "doğru" hissetmediğini anlıyorum, ama bunu yapmanız gerekiyor.
Rob

4
Bence çarpan sabit olmamalı. Kötü bir tasarım.
Borzh

1
@Borzh neden çarpanları aracılığıyla uyarlamalı kısıtlamalar yapmak. Yeniden boyutlandırma ios için.
Bimawa

1
Evet, ayrıca görüntülemenin üst görünüme (hem genişlik / yükseklik hem de genişlik veya yükseklik değil) oranını koruyabilmesi için çarpanı değiştirmek istiyorum, ancak elmanın izin vermediği garip şey. Yani Apple'ın kötü tasarımı, sizin değil.
Borzh

Bu harika bir konuşma ve bunu ve daha fazlasını kapsar youtube.com/watch?v=N5Ert6LTruY
DogCoffee

Yanıtlar:


117

Uygulanması gereken yalnızca iki çarpan kümeniz varsa, iOS8'den itibaren her iki kısıtlama kümesini ekleyebilir ve hangisinin istediğiniz zaman aktif olması gerektiğine karar verebilirsiniz:

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 sürümü

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 Evet, yapabilirsiniz :)
Ja͢ck

7
2'den fazla kısıtlama kümesine ihtiyacınız varsa, istediğiniz sayıda ekleyebilir ve öncelik özelliklerini değiştirebilirsiniz. Bu şekilde, 2 kısıtlama için birini etkin ve diğerini etkin değil olarak ayarlamanız gerekmez, yalnızca önceliği daha yüksek veya daha düşük olarak ayarlarsınız. Yukarıdaki örnekte, diyelim ki 997 olarak standardConstraint önceliğini ayarlayın ve etkin olmasını istediğinizde, zoomConstraint'in önceliğini 995 olarak ayarlayın, aksi takdirde 999'a ayarlayın. Ayrıca XIB'de önceliklerin 1000 olarak ayarlanmadığından emin olun, aksi takdirde XIB onları değiştiremezsiniz ve harika bir çökme yaşarsınız.
Köpek

1
@WonderDog bunun özel bir avantajı var mı? etkinleştirme ve devre dışı bırakma deyimi sindirimi benim için göreli öncelikleri yerine getirmekten daha kolay görünüyor.
Ja͢ck

2
@WonderDog / Jack Kısıtlamaları film şeridinde tanımlandığından yaklaşımlarınızı birleştirerek yaralandım. Hem aynı önceliğe hem de farklı çarpanlara sahip iki zıtlık oluşturduysam, çatıştılar ve bana çok fazla kırmızı verdi. Ve söyleyebileceğimden birini IB aracılığıyla inaktif olarak ayarlayamazsın. Bu nedenle, ana sınırımı 1000 önceliği ile ve 999 önceliği ile canlandırmak istediğim ikincil kısıtımı yarattım (IB'de iyi oynuyor). Sonra bir animasyon bloğunun içinde, birincil kişinin aktif özelliğini false olarak ayarladım ve ikincil olana güzelce animasyon yaptı. Yardımlarınız için teşekkürler!
Clay Garrett

2
Muhtemelen kısıtlamalara güçlü referansları istiyoruz unutmayın aktive & beri, onları devre dışı bırakılması "Devreye veya kısıtlama aramaları devre dışı bırakılması durumunda addConstraint(_:)ve removeConstraint(_:)bu koşullarla yönetilen öğelerin en yakın ortak atası görünümü".
qix

166

Swift'te yeni bir çarpan ayarlamayı oldukça kolaylaştıran bir NSLayoutConstraint uzantısı:

Swift 3.0 ve üzeri sürümlerde

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 kullanımı:

@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])oluşturulmadan önce yapılmalıdır newConstraint, aksi takdirde uyarı ile sonuçlanabilir: Aynı anda kısıtlamalar sağlanamıyor . Ayrıca newConstraint.active = trueberi gereksizdir NSLayoutConstraint.activateConstraints([newConstraint])tam olarak aynı şeyi yapar.
tzaloga

1
etkinleştirmek ve devre dışı bırakmak ios8 önce çalışmıyor, bunun için herhangi bir alternatif var mı ??
narahari_arjun

2
Bu, çarpanı tekrar değiştirirken işe yaramaz, sıfır bir değer alır
J. Doe

4
Bize zaman kazandığınız için teşekkürler! İşlevi yeniden adlandırmak istiyorumcloneWithMultiplier sadece yan etkiler uygulamakla kalmayıp yeni bir kısıtlama döndürdüğünü daha açık hale getirmek için
Alexandre G

5
Çarpanı ayarlarsanız 0.0tekrar güncelleyemeyeceğinizi unutmayın.
Daniel Storm

58

multiplierMülkiyet salt okunur. Eski NSLayoutConstraint'i kaldırmanız ve değiştirmek için yenisiyle değiştirmeniz gerekir.

Bununla birlikte, çarpanı değiştirmek istediğinizi bildiğiniz için, sabitin, genellikle daha az kod olan değişiklik gerektiğinde kendiniz çarparak değiştirebilirsiniz.


64
Çarpanın salt okunur olması tuhaf görünüyor. Bunu beklemiyordum!
jowie

135
@jowie "çarpan" değerinin çarpan değiştirilemezken değiştirilebilmesi ironiktir.
Tomas Andrle

7
Bu yanlış. Bir NSLayoutConstraint, bir yakınlık (in) eşitlik kısıtlaması belirtir. Örneğin: "View1.bottomY = A * View2.bottomY + B" 'A' çarpan, 'B' sabittir. B'yi nasıl ayarlarsanız ayarlayın, A'nın değerini değiştirmekle aynı denklemi üretmez.
Tamás Zahola

8
Tamam @FruityGeek, böylece kullandığınız View2.bottomY'ın şimdiki kısıt en hesaplamak için değeri yeni sabit. Bu kusurludur, çünkü View2.bottomYeski değeri sabitte pişirilecektir, bu nedenle bildirim stilini bırakmanız ve View2.bottomYdeğişiklikleri her zaman manuel olarak güncellemeniz gerekir . Bu durumda manuel düzen de yapabilirsiniz.
Tamás Zahola

2
Bu durumda, NSLayoutConstraint'in ekstra kod olmadan sınır değişikliği veya cihaz döndürmesi için yanıt vermesini istediğimde çalışmaz.
tzaloga

49

Varolan bir düzen kısıtlamasının çarpanını değiştirmek için kullandığım yardımcı işlev . Yeni bir kısıtlama oluşturur ve etkinleştirir ve eskisini devre dışı bırakır.

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

Kullanım, çarpanı 1.2'ye değiştirme:

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

1
İOS 8'den uygulanabilir mi? ya da önceki sürümlerde kullanılabilir
Durai Amuthan.H

1
NSLayoutConstraint.deactivateişlevi yalnızca iOS 8.0 ve üstü sürümlerdir.
Evgenii

Neden iade ediyorsunuz?
mskw

@mskw, işlev oluşturduğu yeni kısıtlama nesnesini döndürür. Bir öncek atılabilir.
Evgenii

42

Andrew Schreiber cevabı için Objective-C Versiyonu

NSLayoutConstraint Class için kategori oluşturun ve yöntemi .h dosyasına böyle ekleyin

    #import <UIKit/UIKit.h>

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

.M dosyasında

#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

Daha sonra ViewController'da güncellemek istediğiniz kısıtlama için bir çıkış oluşturun.

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

ve aşağıdaki gibi istediğiniz yerde çarpanı güncelleyin ..

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

Bu örnek kodda active = trueenablectivate'i etkinleştirmekle aynı şekilde taşıdım ve bu nedenle devre dışı bırakılmadan önce yinelenen bir kısıtlamanın eklenmesine neden oldum , bu bazı senaryolarda bir kısıtlama hatasına neden oluyor.
Jules

13

Biraz matematik ile aynı hedefe ulaşmak için "sabit" özelliğini değiştirebilirsiniz. Kısıtlamadaki varsayılan çarpanınızın 1.0f olduğunu varsayın. Bu kolayca obj-c'ye çevrilebilen Xamarin C # kodudur

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

Bu güzel bir çözüm
J. Doe

3
Yukarıdaki kod yürütüldükten sonra secondItem'in karesi genişlik değiştirirse bu kesilir. SecondItem hiçbir zaman boyutu değiştirmezse tamamdır, ancak secondItem boyutu değiştirirse, kısıtlama artık düzen için doğru değeri hesaplamaz.
John Stephen

John Stephen tarafından işaret edildiği gibi, bu dinamik görünümler, cihazlar için çalışmaz: düzen ile birlikte otomatik olarak güncellenmez.
Womble

1
İkinci öğenin genişliğinin aslında [UIScreen mainScreen] .bounds.size.width (asla değişmez) olduğu durumum için harika bir çözüm
Arik Segal

7

Diğer cevaplarda açıklandığı gibi: Kısıtlamayı kaldırmanız ve yeni bir kısıtlama oluşturmanız gerekir.


Aşağıdakiler için statik yöntemini oluşturarak yeni kısıtlamasını dönen önleyebilirsiniz NSLayoutConstraintile inoutsize geçti kısıtlamayı yeniden atamak için izin verir parametresi,

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
    }

}

Örnek kullanım:

@IBOutlet weak var constraint: NSLayoutConstraint!

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

2

YUKARI KODUN HİÇBİRİ KENDİ KODUMU BU KODU DEĞİŞTİRMEYEN SONRA BENİM İÇİN ÇALIŞMIYOR Bu kod Xcode 10 ve hızlı 4.2'de çalışıyor

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

teşekkürler, iyi çalışıyor gibi görünüyor
Radu Ursache

radu-ursache
Ullas Pujary

2

Evet w çarpan değerlerini değiştirebilir, sadece NSLayoutConstraint'in bir uzantısını yapabilir ve şöyle kullanabilir ->

   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

Diğer birçok yanıtın önerdiği gibi koddaki aktif kısıtlamayı değiştirerek geçiş benim için işe yaramadı. Bu yüzden biri yüklü ve diğeri değil, her iki kodu bağlamak ve sonra birini kaldırarak ve diğerini ekleyerek 2 kısıtlamalar oluşturdu.

Tamlık uğruna, kısıtlamayı bağlamak için kısıtlamayı farenin sağ düğmesini kullanarak diğer grafik öğelerinde olduğu gibi koda sürükleyin:

resim açıklamasını buraya girin

Bir oran IPad ve diğer oran IPhone adını verdim.

Sonra viewDidLoad, aşağıdaki kodu ekleyin

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

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

XCode 10 ve swift 5.0 kullanıyorum


0

İşte @ Tianfu'nun C #'daki cevabına dayanan bir cevap. Kısıtlamaların etkinleştirilmesini ve devre dışı bırakılmasını gerektiren diğer yanıtlar benim için işe yaramadı.

    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

Çarpanın değerini değiştirmek için aşağıdaki adımları izleyin: 1. gerekli kısıtlama için çıkışlar oluşturun, örneğin someConstraint. 2. çarpan değerini someConstraint.constant * = 0.8 gibi değiştirin veya herhangi bir çarpan değeri gibi değiştirin.

bu kadar, mutlu kodlama.


-1

Biri okuyabilir:

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

bu dokümantasyon sayfasında . Bu, çarpanı değiştirebileceği anlamına gelmez (bir var olduğu için)?


var multiplier: CGFloat { get }sadece alıcısı olduğu anlamına gelen tanımdır.
Jonny
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.