NSLayoutConstraint의 승수 속성을 변경할 수 있습니까?


143

하나의 슈퍼 뷰에서 두 개의 뷰를 만든 다음 뷰간에 제약 조건을 추가했습니다.

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

이제 multiplier 속성을 애니메이션으로 변경하고 싶지만 multipler 속성을 변경하는 방법을 알 수 없습니다. (헤더 파일 NSLayoutConstraint.h의 개인 속성에서 _coefficient를 찾았지만 개인입니다.)

멀티플렉서 속성을 어떻게 변경합니까?

내 해결 방법은 이전 제약 조건을 제거하고에 대한 다른 값으로 새 제약 조건을 추가하는 것입니다 multipler.


1
기존 구속 조건을 제거하고 새로운 구속 조건을 추가하는 현재 접근 방식이 올바른 선택입니다. 나는 그것이 "옳다"고 느끼지 않는다는 것을 이해하지만 그것은 당신이 해야하는 방식입니다.
Rob

4
나는 승수가 일정해서는 안된다고 생각합니다. 나쁜 디자인입니다.
Borzh

1
@Borzh 왜 승수를 통해 적응 적 제약을 만듭니다. 크기 조정이 가능한 iOS 용.
Bimawa

1
예, 또한 배율을 변경하여보기가 부모보기 (너비 / 높이가 아니라 너비 또는 높이 모두)에 대한 비율을 유지할 수 있지만 사과가 허용하지 않는 이상한 일입니다. 나는 그것이 당신의 것이 아니라 애플의 나쁜 디자인이라는 것을 의미합니다.
Borzh

이것은 대단한 이야기이며 이것과 더 많은 것을 포함합니다. youtube.com/watch?v=N5Ert6LTruY
DogCoffee

답변:


117

적용해야 할 승수 세트가 두 개만있는 경우 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 ...]

스위프트 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 예, 당신은 할 수 있습니다 :)
Ja͢ck

7
구속 조건 세트가 두 개 이상 필요한 경우 원하는만큼 추가하고 우선 순위 특성을 변경할 수 있습니다. 이 방법으로 제약 조건 2 개에 대해 하나를 활성으로 설정하고 다른 하나를 비활성으로 설정할 필요가 없으며 우선 순위를 높이거나 낮 춥니 다. 위의 예에서 standardConstraint 우선 순위를 997로 설정하고 활성화하려면 zoomedConstraint의 우선 순위를 995로 설정하고 그렇지 않으면 999로 설정하십시오. 또한 XIB의 우선 순위가 1000으로 설정되어 있지 않은지 확인하십시오. 당신은 그것들을 바꿀 수 없으며 멋진 충돌을 경험할 것입니다.
Dog

1
@WonderDog는 특별한 이점이 있습니까? 활성화 및 비활성화의 관용구는 상대적 우선 순위를 해결하는 것보다 소화하기가 더 쉬운 것 같습니다.
Ja͢ck

2
@WonderDog / Jack 스토리 보드에 제약 조건이 정의되어 있기 때문에 접근법을 결합하여 상처를 입었습니다. 그리고 우선 순위가 같지만 승수가 다른 두 개의 제약 조건을 만들면 충돌이 발생하여 많은 빨간색을 얻었습니다. 그리고 내가 말할 수있는 것은 IB를 통해 비활성으로 설정할 수 없습니다. 그래서 우선 순위 1000으로 주요 제약 조건과 우선 순위 999로 애니메이션을 적용하려는 보조 제약 조건을 만들었습니다 (IB에서 훌륭하게 재생 됨). 그런 다음 애니메이션 블록 내에서 기본 속성의 활성 속성을 false로 설정하고 보조 속성으로 멋지게 애니메이션했습니다. 도와 주셔서 감사합니다!
Clay Garrett

2
당신은 아마 당신의 제약에 대한 강한 참조를 원하는 명심 활성화 및 이후를 비활성화 "활성화 또는 제약 호출을 비활성화하는 경우 addConstraint(_:)removeConstraint(_:)이 제약에 의해 관리 항목의 가장 가까운 공통 조상 인 뷰".
qix

166

Swift의 NSLayoutConstraint 확장 기능은 새로운 승수 설정을 매우 쉽게 만듭니다.

스위프트 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 이전에 활성화 및 비활성화가 작동하지 않습니다. 그 대안이 있습니까 ??
narahari_arjun

2
이것은 승수를 다시 변경할 때 작동하지 않으며, 0이됩니다
J. Doe

4
시간을 절약 해 주셔서 감사합니다! cloneWithMultiplier부작용을 적용하는 것이 아니라 새로운 제약 조건을 반환한다는 점을보다 명확하게하기 위해 함수의 이름을 바꾸겠습니다.
Alexandre G

5
승수를 설정하면 0.0다시 업데이트 할 수 없습니다.
다니엘 스톰

58

그만큼 multiplier 속성은 읽기 전용입니다. 이전 NSLayoutConstraint를 제거하고 새 NSLayoutConstraint를 교체하여 수정해야합니다.

그러나 승수를 변경하고 싶다는 것을 알고 있으므로 코드가 적은 변경이 필요할 때 직접 곱하여 상수를 변경할 수 있습니다.


64
승수가 읽기 전용이라는 것이 이상해 보입니다. 나는 그것을 기대하지 않았다!
jowie

135
@jowie 승수는 할 수 없지만 "일정한"값을 변경할 수 있다는 것은 아이러니하다.
Tomas Andrle

7
이것은 잘못이다. NSLayoutConstraint는 아핀 (in) equal 제약 조건을 지정합니다. 예를 들면 : "View1.bottomY = A * View2.bottomY + B" 'A'는 승수이고 'B'는 상수입니다. B를 튜닝하는 방법에 관계없이 A의 값을 변경하는 것과 동일한 방정식을 생성하지 않습니다.
Tamás Zahola

8
좋아 @FruityGeek, 그래서 당신이 사용하고있는 View2.bottomY현재 제약의 계산 값을 새로운 상수. View2.bottomY오래된 값이 일정하게 구워 지기 때문에 결함 이 있습니다. 따라서 선언 스타일을 포기하고 View2.bottomY변경 될 때마다 제약 조건을 수동으로 업데이트해야합니다 . 이 경우 수동 레이아웃도 수행 할 수 있습니다.
Tamás 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

왜 돌려 주니?
mskw

@mskw, 함수는 생성하는 새로운 제약 조건 개체를 반환합니다. 이전 것은 버릴 수 있습니다.
Evgenii

42

Andrew Schreiber의 Objective-C 버전 답변

NSLayoutConstraint Class에 대한 카테고리를 작성하고 다음과 같이 .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활성화와 동일하게 비활성화를 옮겼 으므로 비활성화가 호출되기 전에 중복 제약 조건이 추가되어 일부 시나리오에서 제약 조건 오류가 발생합니다.
Jules

13

약간의 수학으로 동일한 목표를 달성하기 위해 "constant"속성을 대신 변경할 수 있습니다. 제약 조건의 기본 승수가 1.0f라고 가정합니다. 이것은 objective-c로 쉽게 번역 될 수있는 Xamarin C # 코드입니다.

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

이것은 좋은 해결책입니다
J. Doe

3
위의 코드가 실행 된 후 secondItem의 프레임이 너비를 변경하면 중단됩니다. secondItem이 크기를 변경하지 않으면 괜찮지 만 secondItem이 크기를 변경하면 구속 조건이 더 이상 레이아웃의 올바른 값을 계산하지 않습니다.
John Stephen

John Stephen이 지적한 것처럼 동적 뷰, 장치에서는 작동하지 않습니다. 레이아웃과 함께 자동으로 업데이트되지 않습니다.
Womble

1
두 번째 항목의 폭이 실제로있는 내 경우를위한 훌륭한 솔루션 [UIScreen mainScreen] .bounds.size.width (변경 결코)
아리 시걸

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 이상에서 작동합니다. 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

radu-ursache 환영
Ullas Pujary

2

네 승수 값을 변경할 수 있습니다. 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

다른 많은 답변에서 제안한대로 코드의 활성 제약 조건을 변경하여 전환하면 나에게 도움이되지 않았습니다. 그래서 하나는 설치되고 다른 하나는 구속되지 않고 두 코드에 바인딩 한 다음 하나를 제거하고 다른 하나를 추가하여 전환합니다.

완전성을 위해 구속 조건을 바인딩하려면 다른 그래픽 요소와 마찬가지로 마우스 오른쪽 버튼을 사용하여 구속 조건을 코드로 드래그합니다.

여기에 이미지 설명을 입력하십시오

나는 하나의 비례 IPad와 다른 비율을 아이폰이라고 지었다.

그런 다음 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 }게터 만 있다는 의미입니다.
Jonny
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.