Чи можу я змінити властивість множника для 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];

Тепер я хочу змінити властивість множника за допомогою анімації, але не можу зрозуміти, як змінити властивість множника. (Я знайшов _coefficient у приватній власності у файлі заголовка NSLayoutConstraint.h, але він є приватним.)

Як змінити властивість множника?

Моє вирішення полягає в тому, щоб усунути старе обмеження і додати нове з іншим значенням для multipler.


1
Ваш сучасний підхід до усунення старого обмеження та додавання нового - це правильний вибір. Я розумію, що це не так "правильно", але це так, як ви повинні це зробити.
Роб

4
Я думаю, що мультиплікатор не повинен бути постійним. Це погана конструкція.
Борж

1
@ Борж, чому через множники я роблю адаптаційні обмеження. Для зміни розміру ios.
Бімава

1
Так, я також хочу змінити множник, щоб перегляди могли підтримувати його відношення до батьківського виду (не ширини / висоти, а просто ширини чи висоти), але дивно, що яблуко цього не дозволяє. Я маю на увазі, що це поганий дизайн Apple, а не ваш.
Борж

Це чудова розмова і охоплює це та інше 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 ...]

Версія 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 Так, ви можете :)
Ja͢ck

7
Якщо вам потрібно більше двох наборів обмежень, ви можете додати скільки завгодно і змінити їхні пріоритетні властивості. Таким чином, для двох обмежень вам не потрібно встановлювати один на активний, а інший - неактивний, ви просто встановлюєте пріоритет вище або нижче. На наведеному вище прикладі встановіть пріоритет standardConstraint на, скажімо, 997, а коли ви хочете, щоб він був активним, просто встановіть пріоритет zoomedConstraint на 995, інакше встановіть його на 999. Також переконайтесь, що для XIB немає пріоритетів, встановлених у 1000, інакше ви не зможете їх змінити, і ви отримаєте приголомшливий збій.
Собака

1
@WonderDog, але це має якусь особливу перевагу? ідіома ввімкнення та відключення здається мені легшою для засвоєння, ніж необхідність опрацювати відносні пріоритети.
Ja͢ck

2
@WonderDog / Jack Я завершив комбінування ваших підходів, тому що моє обмеження було визначене в раскадровці. І якщо я створив два протипоказання, обидва з однаковим пріоритетом, але різні мультиплікатори, вони конфліктували і дали мені багато червоного. І з того, що я можу сказати, ви не можете встановити його як неактивне через ІБ. Тому я створив основне обмеження з пріоритетом 1000 і моє вторинне обмеження, яке я хочу оживити з пріоритетом 999 (грає добре в ІБ). Потім всередині блоку анімації я встановив активне властивість первинного значення false, і воно добре анімувало вторинне. Дякую обом за допомогу!
Клей Гарретт

2
Майте на увазі, що ви, мабуть, хочете чітких посилань на свої обмеження, якщо активувати та деактивувати їх, оскільки "Активізація або деактивація викликів обмеження addConstraint(_:)та removeConstraint(_:)вигляд, який є найближчим спільним предком елементів, якими керує це обмеження".
qix

166

Ось розширення NSLayoutConstraint у Swift, що робить налаштування нового множника досить простим:

У 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, чи є альтернатива для цього ??
narahari_arjun

2
Це не працює при повторному зміні множника, він отримує нульове значення
J. Doe

4
Дякуємо, що заощадили нам час! Я б перейменував цю функцію на щось подібне, cloneWithMultiplierщоб зробити її більш явною, що вона не просто застосовує побічні ефекти, а повертає нове обмеження
Alexandre G

5
Зауважте, що якщо встановити множник, 0.0ви не зможете оновити його знову.
Даніель Шторм

58

multiplierВластивість тільки для читання. Вам потрібно видалити старий NSLayoutConstraint і замінити його новим, щоб змінити його.

Однак, оскільки ви знаєте, що хочете змінити множник, ви можете просто змінити константу, помноживши її самостійно, коли потрібні зміни, які часто менше коду.


64
Чи не дивно, що множник читається тільки. Я цього не очікував!
jowie

135
@jowie Іронічно, що значення "константа" можна змінювати, коли множник не може.
Томаш Андрле

7
Це неправильно. NSLayoutConstraint задає афінне (не) обмеження рівності. Наприклад: "View1.bottomY = A * View2.bottomY + B" "A" - множник, "B" - константа. Як би ви не налаштували B, це не призведе до того ж рівняння, що і зміна значення A.
Тамас Захола

8
@FruityGeek добре, так що ви використовуєте View2.bottomY«s поточного значення для розрахунку обмеження в новій константі. Це хибно, оскільки View2.bottomYстаре значення буде випікатися постійно, тому вам доведеться відмовитися від декларативного стилю та вручну оновити обмеження в будь-який час View2.bottomY. У цьому випадку ви також можете зробити макет вручну.
Тамас Захола

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? або його можна використовувати в більш ранніх версіях
Дурай Амутан.H

1
NSLayoutConstraint.deactivateфункція лише iOS 8.0+.
Євгеній

Чому ти повертаєш його?
mskw

@mskw, функція повертає новий об'єкт обмеження, який він створює. Попередній можна відкинути.
Євгеній

42

Версія 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це те саме, що активувати, і тому спричиняє додавання дублюючого обмеження до виклику деактивації, це спричиняє помилку обмежень у деяких сценаріях.
Жуль

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 змінить розмір, то обмеження більше не буде обчислювати правильне значення для макета.
Джон Стівен

Як вказував Джон Стівен, це не працюватиме для динамічних поглядів, пристроїв: воно не оновлюється автоматично разом із компонуванням.
Womble

1
Прекрасне рішення для мого випадку, коли ширина другого елемента насправді [UIScreen mainScreen] .bounds.size.width (що ніколи не змінюється)
Арік Сегал

7

Як і в інших відповідях, пояснених: Вам потрібно зняти обмеження та створити нове.


Ви можете уникнути повернення нового обмеження, створюючи статичний метод для NSLayoutConstraintз 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

вітаю раду-уршаче
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

Переключившись, змінивши активне обмеження в коді, як запропоновано багатьма іншими відповідями, для мене не вийшло. Отже, я створив 2 обмеження, один встановлений, а другий не, прив’язуємо обидва до коду, а потім перемикаємось, видаляючи одне і додаючи інше.

Для повноти прив'язування обмеження перетягніть обмеження в код за допомогою правої кнопки миші, як і будь-який інший графічний елемент:

введіть тут опис зображення

Я назвав одну пропорціюIPad, а іншу пропорціюIPhone.

Потім додайте наступний код у viewDidLoad

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

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

Я використовую xCode 10 та swift 5.0


0

Ось відповідь, заснована на відповіді @ Tianfu на C #. Інші відповіді, які вимагають активації та деактивації обмежень, для мене не спрацювали.

    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. Створіть розетки для необхідного обмеження, наприклад, деякогоЗабезпечення. 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 }це визначення, яке означає, що у нього є тільки геттер.
Джоні
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.