ฉันสามารถเปลี่ยนคุณสมบัติตัวคูณสำหรับ 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
วิธีการในปัจจุบันของคุณในการลบข้อ จำกัด เก่าและเพิ่มใหม่เป็นตัวเลือกที่เหมาะสม ฉันเข้าใจว่ามันไม่รู้สึก "ถูก" แต่เป็นวิธีที่คุณควรทำ
Rob

4
ฉันคิดว่าตัวคูณไม่น่าจะคงที่ มันเป็นการออกแบบที่ไม่ดี
Borzh

1
@ Borzh ทำไมผ่านตัวคูณฉันสร้างข้อ จำกัด การปรับตัว สำหรับ ios ที่ปรับขนาดได้
Bimawa

1
ใช่ฉันต้องการเปลี่ยนตัวทวีคูณเพื่อให้มุมมองสามารถรักษาอัตราส่วนให้เป็นมุมมองหลัก (ไม่ใช่ทั้งความกว้าง / ความสูง แต่เพียงความกว้างหรือความสูง) แต่สิ่งแปลกประหลาดที่แอปเปิ้ลไม่อนุญาต ฉันหมายความว่ามันเป็นการออกแบบที่ไม่ดีของ Apple ไม่ใช่ของคุณ
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 ...]

รุ่น 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
หากคุณต้องการข้อ จำกัด มากกว่า 2 ชุดคุณสามารถเพิ่มได้มากเท่าที่คุณต้องการและเปลี่ยนคุณสมบัติที่มีความสำคัญ ด้วยวิธีนี้สำหรับข้อ จำกัด 2 ข้อที่คุณไม่จำเป็นต้องตั้งค่าเป็นใช้งานอยู่และอีกข้อ จำกัด เป็นไม่ใช้งานคุณเพียงแค่ตั้งค่าลำดับความสำคัญให้สูงขึ้นหรือต่ำลง จากตัวอย่างด้านบนให้ตั้งค่าระดับความสำคัญมาตรฐานให้เป็นสมมติว่า 997 และเมื่อคุณต้องการให้มันใช้งานได้เพียงตั้งค่าระดับความสำคัญของ zoomedConstraint เป็น 995 หรือตั้งค่าเป็น 999 นอกจากนี้ให้แน่ใจว่า XIB ไม่มีลำดับความสำคัญเป็น 1,000 คุณจะไม่สามารถเปลี่ยนแปลงพวกเขาและคุณจะได้รับความผิดพลาดที่น่ากลัว
สุนัข

1
@WonderDog นั้นมีข้อได้เปรียบโดยเฉพาะอย่างไร สำนวนของการเปิดใช้งานและการปิดการใช้งานดูเหมือนว่าจะย่อยง่ายขึ้นกว่าที่จะต้องจัดลำดับความสำคัญญาติ
Ja͢ck

2
@WonderDog / Jack ฉันรวมวิธีการของคุณเข้าด้วยกันเพราะข้อ จำกัด ของฉันถูกกำหนดไว้ในกระดานเรื่องราว และถ้าฉันสร้างสอง contraints ทั้งสองที่มีลำดับความสำคัญเท่ากัน แต่ตัวคูณที่แตกต่างกันพวกเขาขัดแย้งกันและให้สีแดงแก่ฉันมากมาย และจากสิ่งที่ฉันสามารถบอกได้ว่าคุณไม่สามารถตั้งค่าให้ไม่ใช้งานผ่าน IB ดังนั้นฉันจึงสร้างข้อ จำกัด หลักของฉันด้วยลำดับความสำคัญ 1,000 และข้อ จำกัด รองที่ฉันต้องการทำให้เคลื่อนไหวโดยมีลำดับความสำคัญ 999 (เล่นได้ดีใน IB) จากนั้นภายในบล็อกแอนิเมชั่นฉันตั้งค่าคุณสมบัติการใช้งานหลักให้เป็นเท็จและเคลื่อนไหวได้ดีกับบล็อกรอง ขอบคุณทั้งคู่สำหรับความช่วยเหลือ!
Clay Garrett

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คุณจะไม่สามารถอัปเดตอีกครั้งได้
Daniel Storm

58

multiplierคุณสมบัติอ่านเท่านั้น คุณต้องลบ NSLayoutConstraint เก่าและแทนที่ด้วยอันใหม่เพื่อแก้ไข

อย่างไรก็ตามเนื่องจากคุณรู้ว่าคุณต้องการเปลี่ยนตัวคูณคุณสามารถเปลี่ยนค่าคงที่ได้ด้วยการคูณมันด้วยตัวคุณเองเมื่อต้องการการเปลี่ยนแปลงซึ่งมักจะเป็นรหัสน้อย


64
ดูเหมือนว่าจะเป็นตัวคูณที่อ่านได้อย่างเดียวเท่านั้น ฉันไม่ได้คาดหวังอย่างนั้น!
jowie

135
@ โจวี่เป็นเรื่องน่าขันที่ค่า "คงที่" สามารถเปลี่ยนแปลงได้ในขณะที่ตัวคูณไม่สามารถทำได้
Tomas Andrle

7
นี่เป็นสิ่งที่ผิด NSLayoutConstraint ระบุข้อ จำกัด ความเท่าเทียมกันของ affine (เป็น) ตัวอย่างเช่น: "View1.bottomY = A * View2.bottomY + B" 'A' เป็นตัวคูณ 'B' คือค่าคงที่ ไม่ว่าคุณจะปรับ B เป็นอย่างไรมันจะไม่สร้างสมการเดียวกับการเปลี่ยนค่าของ A
Tamás Zahola

8
@FruityGeek เอาล่ะเพื่อให้คุณใช้View2.bottomY's ปัจจุบันค่าการคำนวณข้อ จำกัด ของใหม่อย่างต่อเนื่อง มีข้อบกพร่องเนื่องจาก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

Objective-C Version สำหรับคำตอบของ Andrew Schreiber

สร้างหมวดหมู่สำหรับ 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 # ซึ่งสามารถแปลไปยัง object-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 (ที่ไม่เคยเปลี่ยนแปลง) จริง ๆ
Arik Segal

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

ยินดีต้อนรับ 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 และรวดเร็ว 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. สร้างช่องสำหรับข้อ จำกัด ที่จำเป็นเช่น 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.