Gizli UIViews ile Otomatik Düzenleme?


164

İş mantığına bağlı olarak UIViews, çoğu zaman göstermek / gizlemek oldukça yaygın bir paradigma gibi hissediyorum UILabels. Benim sorum, gizli görünümlere kendi çerçeve 0x0 gibi yanıt vermek için AutoLayout kullanarak en iyi yolu nedir. İşte 1-3 özellikten oluşan dinamik bir listeye bir örnek.

Dinamik özellikler listesi

Şu anda düğmeden son etikete kadar 10px'lik bir üst alanım var, bu da etiket gizlendiğinde yukarı kaymayacak. Şu an itibariyle, bu kısıtlamaya bir çıkış oluşturdum ve kaç etiketi görüntülediğime bağlı olarak sabiti değiştirdim. Düğmeyi gizli karelerin üzerinde yukarı itmek için negatif sabit değerler kullandığım için bu açıkçası biraz acayip. Aynı zamanda kötü çünkü gerçek mizanpaj öğeleriyle sınırlandırılmıyor, sadece bilinen yüksekliklere / diğer unsurların dolgusuna dayanan sinsi statik hesaplamalar ve AutoLayout'un ne için üretildiğine karşı savaşıyor.

Açıkçası sadece dinamik etiketlerime bağlı olarak yeni kısıtlamalar yaratabilirdim, ancak bu, bazı boşlukları daraltmaya çalışmak için çok fazla mikro yönetim ve çok fazla ayrıntı. Daha iyi yaklaşımlar var mı? Çerçeve boyutunu 0,0 değiştirme ve AutoLayout'un sınırlamaların manipülasyonu olmadan işini yapmasına izin verme? Görünümler tamamen kaldırılsın mı?

Dürüst olmak gerekirse, sadece gizli görünümün bağlamından sabiti değiştirmek, basit hesaplama ile tek bir kod satırı gerektirir. İle yeni kısıtlamalar yeniden oluşturmak constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:çok ağır görünüyor.

Şubat 2018'i düzenle : Ben'in UIStackViews


Bu soru için teşekkürler Ryan. Sorduğun gibi davalar için ne yapacağımı deliriyordum. Everylay i autolayout için öğretici için kontrol, çoğu diyor ben biraz zor yapmak bulmak raywenderlich öğretici sitesine bakın diyor.
Nassif

Yanıtlar:


82

UIStackViewmuhtemelen iOS 9 ve sonraki sürümlere geçmenin yolu. Gizli görünümü işlemekle kalmaz, doğru şekilde ayarlandığında ek boşlukları ve kenar boşluklarını da kaldırır.


8
UITableViewCell'de UIStackView kullanırken dikkatli olun. Benim için, görünüm çok karmaşık hale geldiğinde, kaydırma, bir hücre her içeri / dışarı kaydırıldığında kekeleyecek şekilde dalgalı olmaya başladı. Kapakların altında StackView kısıtlamalar ekler / kaldırır ve bu düzgün kaydırma için yeterince verimli olmayacaktır.
Kento

7
Yığın görünümünün bunu nasıl ele aldığına dair küçük bir örnek vermek güzel olurdu.
lostintranslation

@Kento, bu hala iOS 10'da bir sorun mu?
Crashalot

3
Beş yıl sonra ... bunu güncelleyip kabul edilen cevap olarak ayarlamalı mıyım? Şimdi üç serbest bırakma döngüsü için mevcuttu.
Ryan Romanchuk

1
@RyanRomanchuk Yapmalısınız, bu kesinlikle en iyi cevap. Teşekkürler Ben!
Duncan Luk

234

Görünümleri gösterme / gizleme konusundaki kişisel tercihim, uygun genişlik veya yükseklik kısıtlamasına sahip bir IBOutlet oluşturmaktır.

Sonra güncelleme constantdeğer 0değer göstermek olmalıdır olursa olsun gizlemek için veya.

Bu tekniğin en büyük avantajı göreceli kısıtlamaların sürdürülmesidir. Diyelim ki yatay görüşünüz x ve A görüşünüz var . Görünüm A genişliği constantolarak ayarlandığında, 0.fB görünümü bu alanı doldurmak için sola hareket eder.

Ağır bir işlem olan kısıtlamalar eklemeye veya kaldırmaya gerek yoktur. Kısıtlamaların güncellenmesi sadece constantişe yarayacaktır.


1
kesinlikle. Bu örnekte - B görünümünü yatay bir boşluk kullanarak A görünümünü sağa konumlandırdığınız sürece. Bu tekniği her zaman kullanıyorum ve çok iyi çalışıyor
Max MacLeod

9
@MaxMacLeod Sadece emin olmak için: iki görünüm arasındaki boşluk x ise, B görünümünün A konumundan başlayabilmesi için boşluk kısıtlama sabitini de 0 olarak değiştirmeliyiz, değil mi?
Mart'ta çekirdek

1
@ kernix evet, iki görünüm arasında yatay bir boşluk kısıtlaması olması gerekiyorsa. sabit 0 elbette boşluk olmadığı anlamına gelir. Dolayısıyla A görünümünü genişliği 0 olarak ayarlayarak B görünümünü kapla aynı hizada olacak şekilde sola hareket ettirirsiniz. Btw yukarı oy için teşekkürler!
Max MacLeod

2
@MaxMacLeod Cevabınız için teşekkürler. Bunu diğer görünümleri içeren bir UIView ile yapmayı denedim, ancak yükseklik sınırını 0 olarak ayarladığımda bu görünümün alt görünümleri gizli değil. Bu çözüm alt görünümler içeren görünümlerle çalışacak mı? Teşekkürler!
shaunlim

6
Ayrıca diğer görüşleri içeren bir UIView için çalışan bir çözümü takdir ediyorum ...
Mark Gibaud

96

Gizlendiğinde bir sabiti 0ve tekrar gösterirseniz başka bir sabiti kullanmanın çözümü işlevseldir, ancak içeriğinizin esnek bir boyuta sahip olması tatmin edici değildir. Esnek içeriğinizi ölçmeniz ve sabit bir geri ayarlamanız gerekir. Bu yanlış geliyor ve sunucu veya UI olayları nedeniyle içeriğin boyutu değişiyorsa sorun yaşıyor.

Daha iyi bir çözümüm var.

Buradaki fikir, öğeyi otomatik yerleşim alanı kaplamaması için gizlediğimizde 0 yükseklik kuralını yüksek önceliğe ayarlamaktır.

Bunu şu şekilde yapabilirsiniz:

1. düşük önceliğe sahip arayüz oluşturucuda 0 genişlik (veya yükseklik) ayarlayın.

ayar genişliği 0 kuralı

Arabirim Oluşturucu, öncelik düşük olduğu için çakışmalar hakkında bağırmaz. Yükseklik davranışını önceliği geçici olarak 999 olarak ayarlayarak test edin (1000'in programlı olarak mutasyona uğraması yasaktır, bu yüzden kullanmayacağız). Arayüz oluşturucu muhtemelen şimdi çelişkili kısıtlamalar hakkında bağırır. İlgili nesnelerdeki öncelikleri 900'e kadar ayarlayarak bunları düzeltebilirsiniz.

2. Koddaki genişlik sınırlamasının önceliğini değiştirebilmeniz için bir çıkış ekleyin:

çıkış bağlantısı

3. Öğenizi gizlerken önceliği ayarlayın:

cell.alertTimingView.hidden    = place.closingSoon != true
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999

@SimplGy lütfen bana bu yerin ne olduğunu söyleyebilir misin.
Niharika

Genel çözüm, @Niharika'nın bir parçası değil, Benim durumumda sadece iş kuralı buydu (restoran yakında kapanıyorsa / göstermiyorsa görünümü gösterin).
SimplGy

11

Bu durumda, Yazar etiketinin yüksekliğini uygun bir IBOutlet ile eşleştiriyorum:

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;

ve kısıtlamanın yüksekliğini 0.0f olarak ayarladığımda, "doldurmayı" koruyoruz, çünkü Oynat düğmesinin yüksekliği buna izin veriyor.

cell.authorLabelHeight.constant = 0; //Hide 
cell.authorLabelHeight.constant = 44; //Show

resim açıklamasını buraya girin


9

Burada bir çok çözüm var ama her zamanki yaklaşımım yine farklı :)

Jorge Arimany ve TMin'in cevabına benzer iki kısıtlama seti oluşturun:

resim açıklamasını buraya girin

İşaretli üç kısıtlamanın tümü, Constant için aynı değere sahiptir. A1 ve A2 işaretli kısıtlamaların Öncelikleri 500, B işaretli kısıtlamada Öncelik 250 olarak ayarlanmıştır (veya UILayoutProperty.defaultLowkodda).

Kısıt B'yi bir IBOutlet'e bağlayın. Ardından, öğeyi gizlediğinizde, kısıtlama önceliğini yüksek (750) olarak ayarlamanız gerekir:

constraintB.priority = .defaultHigh

Ancak öğe görünür olduğunda önceliği tekrar düşük (250) olarak ayarlayın:

constraintB.priority = .defaultLow

Sadece isActiveB kısıtlaması için bu yaklaşımın (kuşkusuz küçük) avantajı , geçici eleman görünümden başka yollarla kaldırılırsa, hala bir çalışma kısıtlamanızın olmasıdır.


Bunun, Öykü Penceresinde yinelenen değerlere sahip olmaması ve öğe yüksekliği / genişliği gibi kodların avantajları vardır. Çok zarif.
Robin Daugherty

Teşekkürler!! Bana yardımcı oldu
Parth Barot

Büyük Yaklaşım, Teşekkür ederim
Basil

5

Görünümü alt sınıflandırın ve geçersiz kılın func intrinsicContentSize() -> CGSize. CGSizeZeroGörünüm gizliyse geri dönmeniz yeterlidir.


2
Bu harika. Bazı UI öğelerinin de tetikleyici olması gerekir invalidateIntrinsicContentSize. Bunu geçersiz kılınarak yapabilirsiniz setHidden.
DrMickeyLauer

5

UILabel'in yer kaplamaması için onu gizlemeniz ve metnini boş bir dizeye ayarlamanız gerektiğini öğrendim. (iOS 9)

Bu gerçeği / hatayı bilmek, bazı insanların düzenlerini basitleştirmesine yardımcı olabilir, muhtemelen orijinal sorunun bile, bu yüzden onu göndereceğimi düşündüm.


1
Gizlemen gerektiğini sanmıyorum. Metnini boş bir dizeye ayarlamak yeterli olmalıdır.
Rob VS

4

UIKitİstenen bu davranış için daha zarif bir yaklaşımın bulunmamasına şaşırdım . Yapmak istemek çok yaygın bir şey gibi görünüyor.

Kısıtlamaları bağlamak IBOutletsve sabitlerini yucky'ye ayarlamak 0(ve NSLayoutConstraintgörüşünüzün alt görünümleri olduğunda uyarılara neden olmak) için, Otomatik Mizanpaj kısıtlamaları olan bir şeyi gizlemek / göstermek için basit ve durumlu bir yaklaşım sağlayan bir uzantı oluşturmaya karar verdimUIView

Yalnızca görünümü gizler ve dış kısıtlamaları kaldırır. Görünümü tekrar gösterdiğinizde, kısıtlamalar geri eklenir. Tek uyarı, çevredeki görünümlere esnek yük devretme kısıtlamaları belirtmeniz gerektiğidir.

Düzenle Bu yanıt iOS 8.4 ve önceki sürümleri hedeflemektedir. İOS 9'da UIStackViewyaklaşımı kullanın .


1
Bu da benim tercih ettiğim yaklaşım. Buradaki diğer cevaplar için önemli bir gelişme çünkü görünümü sıfır boyutta ezmek değil. Squash yaklaşımı, oldukça önemsiz ödemeler dışında herhangi bir şey için sorunlara sahip olacak. Bunun bir örneği, gizli öğenin bu diğer yanıtta bulunduğu büyük boşluktur . Ve belirtildiği gibi kısıtlama ihlallerine neden olabilirsiniz.
Benjohn

@DanielSchlaug Bunu işaret ettiğiniz için teşekkür ederiz. Lisansı MIT olarak güncelledim. Ancak, birisi bu çözümü her uyguladığında zihinsel bir beşlik almam gerekir.
Albert Bori

4

En iyi uygulama, her şey doğru düzen kısıtlamalarına sahip olduğunda, çevredeki görünümlerin nasıl hareket etmesini ve kısıtlamayı bir IBOutletözelliğe bağlamasını istediğinize bağlı olarak bir yükseklik veya kısıtlama ekleyin .

Mülklerinizin strong

Kodda, sabiti 0 olarak ayarlamanız ve etkinleştirmeniz , içeriği gizlemeniz veya içeriği göstermek için devre dışı bırakmanız yeterlidir . Bu, sabit değerle tasarruf etmek ve geri yüklemek için daha iyidir. Daha layoutIfNeededsonra aramayı unutmayın .

Gizlenecek içerik gruplandırılmışsa, en iyi uygulama tümünü bir görünüme koymak ve bu görünüme kısıtlamalar eklemektir.

@property (strong, nonatomic) IBOutlet UIView *myContainer;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *myContainerHeight; //should be strong!!

-(void) showContainer
{
    self.myContainerHeight.active = NO;
    self.myContainer.hidden = NO;
    [self.view layoutIfNeeded];
}
-(void) hideContainer
{
    self.myContainerHeight.active = YES;
    self.myContainerHeight.constant = 0.0f;
    self.myContainer.hidden = YES;
    [self.view layoutIfNeeded];
}

Kurulumunuzu yaptıktan sonra, sınırınızı 0 olarak ayarlayıp orijinal değerine geri döndürerek IntefaceBuilder'da test edebilirsiniz. Diğer kısıtlama önceliklerini kontrol etmeyi unutmayın, bu yüzden gizlendiğinde hiçbir çatışma olmaz. test etmenin diğer bir yolu da 0'a koymak ve önceliği 0'a ayarlamaktır, ancak tekrar en yüksek önceliğe geri yüklemeyi unutmamalısınız.


1
Çünkü kısıtlama ve görünüm her zaman görünüm hiyerarşisi tarafından korunamaz, bu nedenle kısıtlamayı devre dışı bırakır ve görünümü gizlerseniz, görünüm hiyerarşinizin neyi saklamaya karar verdiklerine bakılmaksızın onları tekrar göstermeye devam edersiniz.
Jorge Arimany


2

Tercih ettiğim yöntem Jorge Arimany tarafından önerilene çok benziyor.

Birden çok kısıtlama oluşturmayı tercih ederim. İlk olarak, ikinci etiketin görünür olduğu zamanla ilgili kısıtlamalarınızı oluşturun. Düğme ve 2. etiket arasındaki kısıtlama için bir çıkış oluşturun (objc kullanıyorsanız, güçlü olduğundan emin olun). Bu sınırlama, görünür olduğunda düğme ile ikinci etiket arasındaki yüksekliği belirler.

Ardından, ikinci düğme gizlendiğinde düğme ile üst etiket arasındaki yüksekliği belirten başka bir kısıtlama oluşturun. İkinci kısıtlamaya bir çıkış oluşturun ve bu çıkışın güçlü bir işaretçisi olduğundan emin olun. Ardından installed, arayüz oluşturucudaki onay kutusunun işaretini kaldırın ve ilk kısıtlamanın önceliğinin bu ikinci kısıtlama önceliğinden düşük olduğundan emin olun.

Son olarak, ikinci etiketi gizlediğinizde, .isActivebu kısıtlamaların özelliğini değiştirin ve arayınsetNeedsDisplay()

İşte bu, Sihirli sayılar yok, matematik yok, açmak ve kapatmak için birden fazla kısıtlamanız varsa, çıkış koleksiyonlarını devlet tarafından organize tutmak için bile kullanabilirsiniz. (AKA, tüm gizli kısıtlamaları bir OutletCollection'da ve gizli olmayanları bir diğerinde saklar ve her koleksiyon için .isActive durumlarını değiştirerek tekrarlar).

Ryan Romanchuk'un birden fazla kısıtlama kullanmak istemediğini söylediğini biliyorum, ama bunun mikromanage-y olmadığını ve programsal olarak dinamik olarak görünüm ve kısıtlamalar yarattığını daha basit hissediyorum (bu, eğer ben soruyu doğru okuyorum).

Basit bir örnek oluşturdum, umarım faydalı olur ...

import UIKit

class ViewController: UIViewController {

    @IBOutlet var ToBeHiddenLabel: UILabel!


    @IBOutlet var hiddenConstraint: NSLayoutConstraint!
    @IBOutlet var notHiddenConstraint: NSLayoutConstraint!


    @IBAction func HideMiddleButton(_ sender: Any) {

        ToBeHiddenLabel.isHidden = !ToBeHiddenLabel.isHidden
        notHiddenConstraint.isActive = !notHiddenConstraint.isActive
        hiddenConstraint.isActive = !hiddenConstraint.isActive

        self.view.setNeedsDisplay()
    }
}

resim açıklamasını buraya girin


0

Ben de benim çözüm sunacak, çeşitlilik sunmak için.) Bence her öğenin genişlik / yükseklik artı bir boşluk için bir çıkış oluşturmak sadece saçma ve kodu, olası hatalar ve komplikasyon sayısı patlar.

Metodum tüm görünümleri kaldırır (benim durumumda UIImageView örnekleri), hangilerinin geri eklenmesi gerektiğini seçer ve bir döngüde her birini geri ekler ve yeni kısıtlamalar oluşturur. Aslında çok basit, lütfen takip edin. İşte bunu yapmak için hızlı ve kirli kodum:

// remove all views
[self.twitterImageView removeFromSuperview];
[self.localQuestionImageView removeFromSuperview];

// self.recipients always has to be present
NSMutableArray *items;
items = [@[self.recipients] mutableCopy];

// optionally add the twitter image
if (self.question.sharedOnTwitter.boolValue) {
    [items addObject:self.twitterImageView];
}

// optionally add the location image
if (self.question.isLocal) {
    [items addObject:self.localQuestionImageView];
}

UIView *previousItem;
UIView *currentItem;

previousItem = items[0];
[self.contentView addSubview:previousItem];

// now loop through, add the items and the constraints
for (int i = 1; i < items.count; i++) {
    previousItem = items[i - 1];
    currentItem = items[i];

    [self.contentView addSubview:currentItem];

    [currentItem mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(previousItem.mas_centerY);
        make.right.equalTo(previousItem.mas_left).offset(-5);
    }];
}


// here I just connect the left-most UILabel to the last UIView in the list, whichever that was
previousItem = items.lastObject;

[self.userName mas_remakeConstraints:^(MASConstraintMaker *make) {
    make.right.equalTo(previousItem.mas_left);
    make.leading.equalTo(self.text.mas_leading);
    make.centerY.equalTo(self.attachmentIndicator.mas_centerY);;
}];

Temiz, tutarlı bir düzen ve boşluk alıyorum. Kodum Masonry kullanıyor, kesinlikle tavsiye ederim: https://github.com/SnapKit/Masonry


0

BoxView'ı deneyin , dinamik düzeni özlü ve okunabilir hale getirir.
Sizin durumunuzda:

    boxView.optItems = [
        firstLabel.boxed.useIf(isFirstLabelShown),
        secondLabel.boxed.useIf(isSecondLabelShown),
        button.boxed
    ]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.