NSLayoutConstraintの乗数プロパティを変更できますか?


142

1つのスーパービューに2つのビューを作成し、ビュー間に制約を追加しました。

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

アニメーションでマルチプライヤプロパティを変更したいのですが、マルチプライヤプロパティを変更する方法がわかりません。(ヘッダーファイルNSLayoutConstraint.hのプライベートプロパティに_coefficientが見つかりましたが、プライベートです。)

複数のプロパティを変更するにはどうすればよいですか?

私の回避策は、古い制約を削除して、の値が異なる新しい制約を追加することですmultipler


1
古い制約を削除して新しい制約を追加するという現在のアプローチは、正しい選択です。「正しい」とは思わないことは承知しておりますが、本来のやり方です。
Rob

4
乗数は一定であってはならない。悪いデザインです。
Borzh

1
@Borzhなぜ乗数を介して適応制約を作成するのですか?サイズ変更可能なiOSの場合。
Bimawa 2015

1
はい、私はまた、ビューが親ビューに対する比率を維持できるように乗数を変更したいのですが(幅/高さの両方ではなく、幅または高さのみ)、Appleがそれを許可しないという奇妙なことです。それはあなたのデザインではなく、Appleのデザインの悪さだ。
ボルジ

これは素晴らしい講演であり、これ以上の内容をカバーしていますyoutube.com/watch?v=N5Ert6LTruY
DogCoffee

回答:


117

適用する必要がある乗数のセットが2つしかない場合、iOS8以降では、両方の制約のセットを追加して、いつアクティブにするかを決定できます。

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バージョン

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、あなたは:)できる
ジャック

7
3セット以上の制約が必要な場合は、必要な数だけ追加して、優先順位のプロパティを変更できます。このように、2つの制約について、一方をアクティブに設定し、もう一方を非アクティブに設定する必要はありません。優先度を高くまたは低く設定するだけです。上記の例では、standardConstraintの優先度を997に設定します。アクティブにしたい場合は、zoomedConstraintの優先度を995に設定します。それ以外の場合は999に設定します。また、XIBの優先度が1000に設定されていないことを確認してください。それらを変更することはできず、素晴らしいクラッシュが発生します。

1
@WonderDogは、それでも特別な利点がありますか?有効化と無効化のイディオムは、相対的な優先順位を算出する必要があるよりも、消化しやすいように思えます。
ジャック・

2
@WonderDog / Jack私の制約はストーリーボードで定義されていたので、あなたのアプローチを組み合わせてしまいました。また、優先度は同じで乗数が異なる2つの制約を作成した場合、それらは競合し、多くの赤を与えました。そして、私が言えることから、IBを介して1つを非アクティブに設定することはできません。そこで、優先度1000でメインの制約を作成し、優先度999でアニメーション化したいセカンダリ制約を作成しました(IBでうまく機能します)。次に、アニメーションブロック内で、プライマリプロパティのアクティブプロパティをfalseに設定し、セカンダリプロパティにうまくアニメートしました。助けてくれてありがとう!
クレイギャレット

2
「制約をアクティブ化または非アクティブ化する場合、addConstraint(_:)およびremoveConstraint(_:)この制約によって管理されるアイテムの最も近い共通の祖先であるビュー上で、制約をアクティブ化または非アクティブ化する」ため、制約への強い参照がおそらく必要になることに注意してください。
qix

166

新しい乗数の設定を非常に簡単にするSwiftのNSLayoutConstraint拡張機能を次に示します。

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

デモの使用法:

@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])を作成する前に実行する必要がありますnewConstraint。そうしないと、警告が発生する場合があります制約を同時に満たすことができません。またnewConstraint.active = true以来不要であるNSLayoutConstraint.activateConstraints([newConstraint])とまったく同じことを行います。
tzaloga

1
ios8の前にactivateとdeactivateは機能しません。その代わりの方法はありますか?
narahari_arjun 2016年

2
これは、乗数を再度変更する場合は機能せず、nil値になります
J. Doe

4
時間を節約してくれてありがとう!cloneWithMultiplier副作用を適用するだけでなく、新しい制約を返すことをより明確にするために、関数の名前を次のような名前に変更します
Alexandre G

5
乗数を設定0.0すると、再度更新できなくなります。
Daniel Storm

57

multiplierプロパティは読み取り専用です。変更するには、古いNSLayoutConstraintを削除し、新しいNSLayoutConstraintを新しいものに置き換える必要があります。

ただし、乗数を変更する必要があることがわかっているため、変更が必要な場合は定数を自分で乗算するだけで変更できます。


64
乗数が読み取り専用であることは奇妙に思われます。期待していなかった!
ジョウイ14

135
@jowie皮肉なことに、定数は変更できますが、乗数は変更できません。
Tomas Andrle 2015

7
これは間違っています。NSLayoutConstraintは、アフィン(不等)制約を指定します。例:「View1.bottomY = A * View2.bottomY + B」「A」は乗数、「B」は定数です。Bをどのように調整しても、Aの値を変更するのと同じ方程式は生成されません。
タマシュZahola

8
@FruityGeekは問題ありません。つまり、View2.bottomY現在の値を使用して、制約の新しい定数を計算しています。View2.bottomY古い値は定数に焼き付けられるため、これには欠陥があります。そのため、宣言的なスタイルをあきらめて、View2.bottomY変更があるたびに手動で制約を更新する必要があります。この場合、手動でレイアウトすることもできます。
タマシュZahola

2
これは、追加のコードなしでNSLayoutConstraintが境界の変更またはデバイスの回転に応答するようにしたい場合には機能しません。
tzaloga

49

既存のレイアウト制約の乗数を変更するために使用するヘルパー関数。新しい制約を作成してアクティブにし、古い制約を非アクティブにします。

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

使用法、乗数を1.2に変更:

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

1
iOS 8から適用可能ですか?または、以前のバージョンで使用できます
Durai Amuthan.H

1
NSLayoutConstraint.deactivate機能はiOS 8.0以降のみです。
Evgenii 2017

なぜ返品するのですか?
mskw

@mskw、関数は作成した新しい制約オブジェクトを返します。以前のものは破棄できます。
Evgenii 2018

42

Andrew Schreiber回答のObjective-Cバージョン

NSLayoutConstraintクラスのカテゴリを作成し、次のように.hファイルにメソッドを追加します

    #import <UIKit/UIKit.h>

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

.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

後でViewControllerで、更新する制約のアウトレットを作成します。

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

以下のように、乗算器を更新します。

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

このサンプルコードでactive = trueは、activateと同じようにdeactivateを移動したため、deactivateが呼び出される前に重複した制約が追加されます。これにより、一部のシナリオで制約エラーが発生します。
ジュール、

13

代わりに「定数」プロパティを変更して、少しの計算で同じ目標を達成できます。制約のデフォルトの乗数が1.0fであると仮定します。これはXamarin C#コードで、objective-cに簡単に変換できます

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

これは素晴らしいソリューションです
J. Doe

3
上記のコードが実行された後、secondItemのフレームの幅が変わると、これは壊れます。secondItemがサイズを変更しない場合は問題ありませんが、secondItemがサイズを変更する場合、制約はレイアウトの正しい値を計算しなくなります。
ジョンスティーブン

John Stephenが指摘したように、これは動的ビューやデバイスでは機能しません。レイアウトとともに自動的に更新されるわけではありません。
Womble

1
2番目の項目の幅が実際には[UIScreen mainScreen] .bounds.size.width(これは変更されない)である私の場合の優れたソリューション
Arik Segal

7

他の回答で説明されているとおり、制約を削除して新しい制約を作成する必要があります。


NSLayoutConstraintwith inoutパラメータの静的メソッドを作成することで、新しい制約が返されないようにすることができます。これにより、渡された制約を再割り当てできます。

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
    }

}

使用例:

@IBOutlet weak var constraint: NSLayoutConstraint!

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

2

上記のコードのどれも、自分のコードを変更しようとした後、このコードで機能しなかったこのコードは、Xcode 10および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() 
 }

ありがとう、
正常

radu-ursacheを歓迎する
Ullas Pujary

2

はいwは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 = shouldBeArchived
        newConstraint.identifier = identifier

        NSLayoutConstraint.activate([newConstraint])
        return newConstraint
    }

            self.mainImageViewHeightMultiplier = self.mainImageViewHeightMultiplier.setMultiplier(375.0/812.0)

1

他の多くの回答で示唆されているように、コード内のアクティブな制約を変更して切り替えると、うまくいきませんでした。したがって、2つの制約を作成しました。1つはインストール済みで、もう1つはインストールせず、両方をコードにバインドしてから、一方を削除してもう一方を追加して切り替えます。

完全を期すために、制約をバインドするには、他のグラフィック要素と同様に、マウスの右ボタンを使用して制約をコードにドラッグします。

ここに画像の説明を入力してください

1つのプロポーションをIPad、もう1つのプロポーションをiPhoneと名付けました。

次に、viewDidLoadで次のコードを追加します。

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

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

xCode 10とSwift 5.0を使用しています


0

これは、C#での@Tianfuの回答に基づく回答です。制約のアクティブ化と非アクティブ化を必要とする他の回答は、私にはうまくいきませんでした。

    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

乗数の値を変更するには、以下の手順に従います。1. someConstraintなど、必要な制約のアウトレットを作成します。2.乗数の値をsomeConstraint.constant * = 0.8のように変更しますまたは任意の乗数値のます。

それで、ハッピーコーディングです。


-1

読むことができます:

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

このドキュメントのページで。これは、乗数を変更できることを意味するのではないですか(varであるため)?


var multiplier: CGFloat { get }これは、ゲッターしか持たないことを意味する定義です。
ジョニー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.