Bisakah saya mengubah properti pengganda untuk NSLayoutConstraint?

142

Saya membuat dua tampilan dalam satu superview, dan kemudian menambahkan batasan antara tampilan:

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

Sekarang saya ingin mengubah properti pengganda dengan animasi, tetapi saya tidak tahu cara mengubah properti pengganda. (Saya menemukan _coefisien dalam properti pribadi di file header NSLayoutConstraint.h, tetapi bersifat pribadi.)

Bagaimana cara mengubah properti multipler?

Solusi saya adalah menghapus batasan lama dan menambahkan yang baru dengan nilai berbeda untuk multipler.

Bimawa
sumber
1
Pendekatan Anda saat ini untuk menghilangkan kendala lama dan menambahkan yang baru adalah pilihan yang tepat. Saya mengerti bahwa itu tidak terasa "benar", tetapi memang seharusnya Anda melakukannya.
Rob
4
Saya pikir pengganda tidak boleh konstan. Ini desain yang buruk.
Borzh
1
@ Borzh mengapa melalui pengganda saya membuat kendala adaptif. Untuk resizeble ios.
Bimawa
1
Ya, saya juga ingin mengubah pengali sehingga tampilan dapat mempertahankan rasio terhadap tampilan induk (bukan lebar / tinggi tetapi hanya lebar atau tinggi), tetapi anehnya apel tidak mengizinkannya. Maksud saya itu desain buruk Apple, bukan milik Anda.
Borzh
Ini adalah pembicaraan yang hebat dan membahas hal ini dan lebih banyak lagi youtube.com/watch?v=N5Ert6LTruY
DogCoffee

Jawaban:

117

Jika Anda hanya memiliki dua set pengganda yang perlu diterapkan, dari iOS8 dan seterusnya Anda dapat menambahkan kedua set kendala dan memutuskan mana yang harus aktif kapan saja:

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 ...]

Versi 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
Mendongkrak
sumber
2
@ micnguyen Ya, Anda bisa :)
Ja͢ck
7
Jika Anda membutuhkan lebih dari 2 set kendala, Anda dapat menambahkan sebanyak yang Anda inginkan dan mengubah properti prioritas mereka. Dengan cara ini, untuk 2 kendala Anda tidak harus menetapkan satu untuk aktif dan yang lainnya sebagai tidak aktif, Anda hanya menetapkan prioritas lebih tinggi atau lebih rendah. Pada contoh di atas, tetapkan prioritas standardConstraint menjadi, katakanlah 997, dan ketika Anda ingin aktif, tetapkan prioritas zoomConstraint menjadi 995, jika tidak, tetapkan ke 999. Juga pastikan XIB tidak memiliki prioritas yang diatur ke 1000, jika tidak Anda tidak akan bisa mengubahnya dan Anda akan mendapatkan crash yang hebat.
Anjing
1
@WonderDog apakah itu memiliki keunggulan tertentu? idiom untuk mengaktifkan dan menonaktifkan menurut saya lebih mudah dicerna daripada harus mengerjakan prioritas relatif.
Ja͢ck
2
@WonderDog / Jack Saya akhirnya menggabungkan pendekatan Anda karena kendala saya didefinisikan di storyboard. Dan jika saya membuat dua kendala, keduanya dengan prioritas yang sama tetapi pengganda yang berbeda, mereka bertentangan dan memberi saya banyak warna merah. Dan dari apa yang saya tahu Anda tidak dapat menetapkan satu sebagai tidak aktif melalui IB. Jadi saya membuat kendala utama saya dengan prioritas 1000 dan kendala sekunder yang ingin saya hidupkan dengan prioritas 999 (bermain bagus di IB). Kemudian di dalam blok animasi, saya mengatur properti aktif primer seseorang menjadi false dan itu animasi baik ke yang sekunder. Terima kasih atas bantuannya!
Clay Garrett
2
Ingatlah bahwa Anda mungkin menginginkan referensi yang kuat untuk kendala Anda jika mengaktifkan & menonaktifkannya, karena "Mengaktifkan atau menonaktifkan panggilan kendala addConstraint(_:)dan removeConstraint(_:)pada pandangan yang merupakan leluhur umum terdekat dari item yang dikelola oleh kendala ini".
qix
166

Berikut ini adalah ekstensi NSLayoutConstraint di Swift yang membuat pengaturan pengganda baru cukup mudah:

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

Penggunaan demo:

@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() 
}
Andrew Schreiber
sumber
11
NSLayoutConstraint.deactivateConstraints([self])harus dilakukan sebelum membuat newConstraint, jika tidak dapat mengakibatkan peringatan: Tidak dapat secara bersamaan memenuhi kendala . Juga newConstraint.active = truetidak perlu karena NSLayoutConstraint.activateConstraints([newConstraint])melakukan hal yang persis sama.
tzaloga
1
aktifkan dan nonaktifkan tidak berfungsi sebelum ios8, apakah ada alternatif untuk itu ??
narahari_arjun
2
Ini tidak berfungsi ketika mengubah pengali lagi, itu mendapat nilai nol
J. Doe
4
Terima kasih telah menghemat waktu kami! Saya akan mengubah nama fungsi menjadi cloneWithMultiplierlebih eksplisit sehingga tidak hanya menerapkan efek samping tetapi mengembalikan batasan baru
Alexandre G
5
Perhatikan bahwa jika Anda mengatur pengganda untuk 0.0Anda tidak akan dapat memperbaruinya lagi.
Daniel Storm
57

Itu multiplier properti baca saja. Anda harus menghapus NSLayoutConstraint lama dan menggantinya dengan yang baru untuk memodifikasinya.

Namun, karena Anda tahu Anda ingin mengubah pengali, Anda bisa mengubah konstanta dengan mengalikannya sendiri saat diperlukan perubahan yang seringkali lebih sedikit kode.

Fruity Geek
sumber
64
Tampaknya aneh bahwa pengali hanya baca. Saya tidak mengharapkan itu!
jowie
135
@ jowie ironis bahwa nilai "konstan" dapat diubah sementara pengali tidak bisa.
Tomas Andrle
7
Ini salah. NSLayoutConstraint menetapkan batasan kesetaraan affine (in). Misalnya: "View1.bottomY = A * View2.bottomY + B" 'A' adalah pengganda, 'B' adalah konstanta. Tidak peduli bagaimana Anda menyetel B, itu tidak akan menghasilkan persamaan yang sama dengan mengubah nilai A.
Tamás Zahola
8
@FruityGeek oke, jadi Anda menggunakan View2.bottomY's saat ini nilai untuk menghitung kendala ini baru konstan. Itu cacat, karena View2.bottomYnilai lama akan dipanggang dalam konstanta, jadi Anda harus melepaskan gaya deklaratif dan secara manual memperbarui batasan kapan saja View2.bottomYperubahan. Dalam hal ini Anda mungkin juga melakukan tata letak manual.
Tamás Zahola
2
Ini tidak akan berfungsi jika saya ingin NSLayoutConstraint untuk merespons perubahan batas atau rotasi perangkat tanpa kode tambahan.
tzaloga
49

Fungsi pembantu saya gunakan untuk mengubah pengali dari batasan tata letak yang ada. Ini menciptakan dan mengaktifkan kendala baru dan menonaktifkan yang lama.

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

Penggunaan, mengubah pengali menjadi 1.2:

constraint = MyConstraint.changeMultiplier(constraint, multiplier: 1.2)
Evgenii
sumber
1
Apakah ini berlaku dari iOS 8? atau dapat digunakan dalam versi sebelumnya
Durai Amuthan.H
1
NSLayoutConstraint.deactivatefungsi hanya iOS 8.0+.
Evgenii
Mengapa Anda mengembalikannya?
mskw
@ mskw, fungsi mengembalikan objek kendala baru yang dibuatnya. Yang sebelumnya bisa dibuang.
Evgenii
42

Versi Objective-C untuk jawaban Andrew Schreiber

Buat kategori untuk NSLayoutConstraint Class dan tambahkan metode dalam file .h seperti ini

    #import <UIKit/UIKit.h>

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

Dalam file .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

Kemudian di ViewController buat outlet untuk batasan yang ingin Anda perbarui.

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

dan perbarui pengganda di mana pun Anda inginkan seperti di bawah ini ..

self.topConstraint = [self.topConstraint updateMultiplier:0.9099];
Koti Tummala
sumber
Saya sudah pindah menonaktifkan kode sampel ini karena active = truesama dengan mengaktifkan dan karena itu menyebabkan kendala duplikat ditambahkan sebelum menonaktifkan disebut, ini menyebabkan kesalahan kendala dalam beberapa skenario.
Jules
13

Anda dapat mengubah properti "konstan" sebagai gantinya untuk mencapai tujuan yang sama dengan sedikit matematika. Asumsikan pengganda default Anda pada kendala adalah 1.0f. Ini adalah kode Xamarin C # yang dapat dengan mudah diterjemahkan ke objektif-c

private void SetMultiplier(nfloat multiplier)
{
    FirstItemWidthConstraint.Constant = -secondItem.Frame.Width * (1.0f - multiplier);
}
Tianfu Dai
sumber
ini adalah solusi yang bagus
J. Doe
3
Ini akan rusak jika frame secondItem berubah lebar setelah kode di atas dijalankan. Tidak apa-apa jika secondItem tidak akan pernah mengubah ukuran, tetapi jika secondItem mengubah ukuran maka kendala tidak akan lagi menghitung nilai yang benar untuk tata letak.
John Stephen
Seperti yang ditunjukkan oleh John Stephen, ini tidak akan berfungsi untuk tampilan dinamis, perangkat: itu tidak secara otomatis diperbarui bersama dengan tata letak.
Womble
1
Solusi hebat untuk kasus saya, di mana lebar item kedua sebenarnya [UIScreen mainScreen] .bounds.size.width (yang tidak pernah berubah)
Arik Segal
7

Seperti dalam jawaban lain yang dijelaskan: Anda harus menghapus kendala dan membuat yang baru.


Anda dapat menghindari mengembalikan batasan baru dengan membuat metode statis NSLayoutConstraintdengan inoutparameter, yang memungkinkan Anda untuk menetapkan kembali batasan yang lewat

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
    }

}

Contoh penggunaan:

@IBOutlet weak var constraint: NSLayoutConstraint!

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

TIDAK ADA KODE DI ATAS YANG BEKERJA BAGI SAYA JADI SETELAH MENCOBA UNTUK MEMODIFIKASI KODE SENDIRI SAYA KODE INI Kode ini berfungsi di Xcode 10 dan 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() 
 }
Ullas Pujary
sumber
terima kasih, tampaknya berfungsi dengan baik
Radu Ursache
selamat datang radu-ursache
Ullas Pujary
2

Ya, kami dapat mengubah nilai pengali hanya membuat ekstensi NSLayoutConstraint dan menggunakannya seperti ->

   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)
Rajan Singh
sumber
1

Beralih dengan mengubah batasan aktif dalam kode seperti yang disarankan oleh banyak jawaban lain tidak berhasil untuk saya. Jadi saya membuat 2 batasan, satu terpasang dan yang lainnya tidak, mengikat keduanya ke kode, dan kemudian beralih dengan menghapus satu dan menambahkan yang lain.

Demi kelengkapan, untuk mengikat konstrain, seret konstrain ke kode menggunakan tombol kanan mouse, sama seperti elemen grafis lainnya:

masukkan deskripsi gambar di sini

Saya menyebutkan satu proporsi proporsi dan satu proporsi proporsi.

Kemudian, tambahkan kode berikut, di viewDidLoad

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

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

Saya menggunakan xCode 10 dan swift 5.0

MiguelSlv
sumber
0

Berikut adalah jawaban berdasarkan jawaban @ Tianfu di C #. Jawaban lain yang memerlukan aktivasi dan penonaktifan kendala tidak berfungsi untuk saya.

    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()
}
RawMean
sumber
0

Untuk mengubah nilai pengali, ikuti langkah-langkah di bawah ini: 1. buat outlet untuk kendala yang diperlukan misalnya, beberapa kendala. 2. ubah nilai pengali seperti someConstraint.constant * = 0.8 atau nilai pengali apa pun.

itu saja, selamat coding.

Gulshan Kumar
sumber
-1

Seseorang dapat membaca:

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

di halaman dokumentasi ini . Bukankah itu berarti bahwa seseorang harus dapat memodifikasi pengganda (karena itu adalah var)?

Michel
sumber
var multiplier: CGFloat { get }adalah definisi yang berarti hanya memiliki pengambil.
Jonny