İPad dikey ve yatay modlar için boyutlandırma sınıfı


97

Temel olarak alt görünümlerimin, xcode 6'da tanıtılan Boyutlandırma Sınıflarını kullanarak iPad'in (Dikey veya Yatay) yönüne bağlı olarak farklı şekilde konumlandırılmasını istiyorum. ancak IB'de iPad için ayrı yatay veya dikey modları kapsayan hiçbiri yok gibi görünüyor. Biri yardım edebilir mi?


Varsayılan ekran için gerekli olan programatik olmayan bir çözüm yok gibi görünüyor
SwiftArchitect

Yanıtlar:


174

Görünüşe göre Apple, her iki iPad yönünü de aynı şekilde ele almak gibi görünüyor - ancak birçoğumuz bulduğumuz gibi, iPad Dikey ve iPad Yatay için UI düzenini değiştirmek istemek için çok meşru tasarım nedenleri var.

Maalesef, mevcut işletim sistemi bu ayrım için destek sağlamıyor gibi görünüyor ... bu, Adaptive UI kullanarak ideal olarak ücretsiz olarak elde edebilmemiz gereken şeyi elde etmek için kodda veya benzer geçici çözümlerde otomatik düzen kısıtlamalarını değiştirmeye geri döndüğümüz anlamına geliyor. .

Zarif bir çözüm değil.

Apple'ın zaten bir boyut sınıfı kullanmak IB ve UIKit yerleşik olduğu sihirli kaldıraç için bir yol yok mudur bizim seçme verilen bir yönlendirme için?

~

Sorunu daha genel bir şekilde düşünürken, 'boyut sınıflarının' IB'de depolanan birden çok düzeni ele almanın basit bir yolu olduğunu fark ettim, böylece çalışma zamanında gerektiğinde çağrılabilirler.

Aslında, 'boyut sınıfı' gerçekten sadece bir enum değer çiftidir. UIInterface.h'den:

typedef NS_ENUM(NSInteger, UIUserInterfaceSizeClass) {
    UIUserInterfaceSizeClassUnspecified = 0,
    UIUserInterfaceSizeClassCompact     = 1,
    UIUserInterfaceSizeClassRegular     = 2,
} NS_ENUM_AVAILABLE_IOS(8_0);

Dolayısıyla, Apple'ın bu farklı varyasyonları adlandırmaya karar verdiği şey ne olursa olsun , temelde, IB'de depolanan bir düzeni diğerinden ayırmak için benzersiz bir tür tanımlayıcısı olarak kullanılan bir çift tam sayıdır.

Şimdi, IB'de (kullanılmayan bir boyut sınıfı kullanarak) alternatif bir düzen oluşturduğumuzu varsayalım - mesela, iPad Portre için ... cihazın , çalışma zamanında ihtiyaç duyulan boyut sınıfı seçimimizi (UI düzeni) kullanmasını sağlamanın bir yolu var mı? ?

Soruna birkaç farklı (daha az zarif) yaklaşım denedikten sonra, varsayılan boyut sınıfını programla geçersiz kılmanın bir yolu olabileceğinden şüphelendim. Ve (UIViewController.h dosyasında):

// Call to modify the trait collection for child view controllers.
- (void)setOverrideTraitCollection:(UITraitCollection *)collection forChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController NS_AVAILABLE_IOS(8_0);

Bu nedenle, görünüm denetleyici hiyerarşinizi bir 'alt' görünüm denetleyicisi olarak paketleyebilir ve bunu en üst düzey bir üst düzey denetleyiciye ekleyebilirseniz ... OS'den.

İşte bunu 'üst' görünüm denetleyicisinde yapan örnek bir uygulama:

@interface RDTraitCollectionOverrideViewController : UIViewController {
    BOOL _willTransitionToPortrait;
    UITraitCollection *_traitCollection_CompactRegular;
    UITraitCollection *_traitCollection_AnyAny;
}
@end

@implementation RDTraitCollectionOverrideViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setUpReferenceSizeClasses];
}

- (void)setUpReferenceSizeClasses {
    UITraitCollection *traitCollection_hCompact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
    UITraitCollection *traitCollection_vRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];
    _traitCollection_CompactRegular = [UITraitCollection traitCollectionWithTraitsFromCollections:@[traitCollection_hCompact, traitCollection_vRegular]];

    UITraitCollection *traitCollection_hAny = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassUnspecified];
    UITraitCollection *traitCollection_vAny = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassUnspecified];
    _traitCollection_AnyAny = [UITraitCollection traitCollectionWithTraitsFromCollections:@[traitCollection_hAny, traitCollection_vAny]];
}

-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    _willTransitionToPortrait = self.view.frame.size.height > self.view.frame.size.width;
}

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]
    _willTransitionToPortrait = size.height > size.width;
}

-(UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController {
    UITraitCollection *traitCollectionForOverride = _willTransitionToPortrait ? _traitCollection_CompactRegular : _traitCollection_AnyAny;
    return traitCollectionForOverride;
}
@end

Çalışıp çalışmadığını görmek için hızlı bir demo olarak, IB'deki alt denetleyici düzeninin 'Normal / Normal' ve 'Kompakt / Normal' sürümlerine özel etiketler ekledim:

görüntü açıklamasını buraya girin görüntü açıklamasını buraya girin

Ve işte iPad her iki yöndeyken çalışıyor gibi görünüyor: görüntü açıklamasını buraya girin görüntü açıklamasını buraya girin

Voila! Çalışma zamanında özel boyut sınıfı yapılandırmaları.

Umarım Apple bunu işletim sisteminin bir sonraki sürümünde gereksiz hale getirir. Bu arada, bu, otomatik mizanpaj kısıtlamalarıyla programatik olarak uğraşmaktan veya kodda başka manipülasyonlar yapmaktan daha zarif ve ölçeklenebilir bir yaklaşım olabilir.

~

DÜZENLEME (6/4/15): Yukarıdaki örnek kodun, tekniği göstermek için esasen bir kavram kanıtı olduğunu lütfen unutmayın. Kendi özel uygulamanız için gerektiği gibi uyarlamaktan çekinmeyin.

~

DÜZENLEME (7/24/15): Yukarıdaki açıklamanın konuyu aydınlatmaya yardımcı olması sevindirici. Test etmemiş olsam da, mohamede1945 tarafından hazırlanan kod [aşağıda], pratik amaçlar için yararlı bir optimizasyon gibi görünüyor. Denemekten çekinmeyin ve ne düşündüğünüzü bize bildirin. (Eksiksizlik adına yukarıdaki örnek kodu olduğu gibi bırakacağım.)


1
Harika gönderi @RonDiamond!
amergin

3
Apple, Safari'de genişlik boyutu sınıfını yeniden tanımlamak için benzer bir yaklaşım benimsiyor, böylece bunun az çok desteklenen bir yaklaşım olduğundan emin olabilirsiniz. Fazladan sarmaşığa gerçekten ihtiyacınız olmamalı; UITraitCollectionyeterince optimize edilmiştir ve overrideTraitCollectionForChildViewControllergenişlik kontrolünü yapıp daha sonra oluşturmanın bir sorun olmaması için yeterince nadiren çağrılır.
zwaldowski

1
@zwaldowski Teşekkürler. Buradaki örnek kod sadece tekniği göstermek içindir ve açıkça istenildiği gibi çeşitli şekillerde optimize edilebilir. (Genel bir kural olarak, bir nesnenin defalarca tekrar tekrar kullanıldığı durumlarda [örneğin, cihaz yönünü değiştirdiğinde], bir nesneye tutunmanın kötü bir fikir olduğunu düşünmüyorum; ama sizin de belirttiğiniz gibi, buradaki performans farkı minimum olabilir.)
RonDiamond

1
@Rashmi: Açıklamada ima ettiğim gibi, sonuçta hangisini seçtiğiniz önemli değil - sadece düzen (ler) i bir çift enum değeriyle eşliyorsunuz. Böylece gerçekten sizin için anlamlı olan "beden sınıfı" nı kullanabilirsiniz. Varsayılan olarak bir yerde miras alınabilecek başka bir (yasal) boyut sınıfıyla çakışmadığından emin olacağım.
RonDiamond

1
Çocuk görüşü denetleyicisine gelince, önemli olduğunu düşünmüyorum. Kapsayıcıya bir alt denetleyici olarak düzgün bir şekilde eklendiği sürece, onu programlı olarak veya bir uçtan başlatabilmelisiniz.
RonDiamond

41

RonDiamond'un çok uzun cevabının bir özeti olarak. Tek yapmanız gereken, kök görünüm denetleyicinizdedir.

Amaç-c

- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
    if (CGRectGetWidth(self.view.bounds) < CGRectGetHeight(self.view.bounds)) {
        return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
    } else {
        return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
    }
}

Swift:

override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection! {
        if view.bounds.width < view.bounds.height {
            return UITraitCollection(horizontalSizeClass: .Compact)
        } else {
            return UITraitCollection(horizontalSizeClass: .Regular)
        }
    }

Ardından storyborad'da Dikey için kompakt genişliği ve Yatay için Normal genişliği kullanın.


Yeni bölünmüş ekran modlarında bunun nasıl sonuçlanacağını merak ediyorum ... öğrenmenin bir yolu!
mm2001

UITraitCollection yalnızca iOS 8.0+ içindir
mohamede1945

Benim için çalışmıyor İki kapsama görünümüne sahip bir görünümüm var, bunların yönüne bağlı olarak farklı bir konumda gösterilmesi gerekiyor. İhtiyaç duyulan tüm boyut sınıflarını oluşturdum, hepsi iPhone için çalışıyor. İPad yönünü de ayırmak için kodunuzu kullanmayı denedim. Ancak, görüşler konusunda tuhaf davranacak. Olması gerektiği gibi değiştirmez. Neler olabileceğine dair bir fikrin var mı?
NoSixties

1
Bunun - (UITraitCollection *)traitCollectionyerine aştığımda bu benim için çalıştı overrideTraitCollectionForChildViewController. Ayrıca kısıtlar özellik koleksiyonlarıyla eşleşmelidir, yani wC (hAny).
H. de Jonge

1
@Apollo isterdim ama asıl soruya cevap vermezdi. Apple'ın setOverrideTraitCollection'ın nasıl kullanılacağına dair örneğine
malhal

5

İPad, hem yatay hem de dikey boyutlar için 'normal' boyut özelliğine sahiptir ve portre ile manzara arasında hiçbir ayrım yapmaz.

Bu boyut özellikleri, özel UIViewControlleralt sınıf kodunuzda yöntem aracılığıyla geçersiz kılınabilir traitCollection, örneğin:

- (UITraitCollection *)traitCollection {
    // Distinguish portrait and landscape size traits for iPad, similar to iPhone 7 Plus.
    // Be aware that `traitCollection` documentation advises against overriding it.
    UITraitCollection *superTraits = [super traitCollection];
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        UITraitCollection *horizontalRegular = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
        UITraitCollection *verticalRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];
        UITraitCollection *regular = [UITraitCollection traitCollectionWithTraitsFromCollections:@[horizontalRegular, verticalRegular]];

        if ([superTraits containsTraitsInCollection:regular]) {
            if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])) {
                // iPad in portrait orientation
                UITraitCollection *horizontalCompact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
                return [UITraitCollection traitCollectionWithTraitsFromCollections:@[superTraits, horizontalCompact, verticalRegular]];
            } else {
                // iPad in landscape orientation
                UITraitCollection *verticalCompact = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact];
                return [UITraitCollection traitCollectionWithTraitsFromCollections:@[superTraits, horizontalRegular, verticalCompact]];
            }
        }
    }
    return superTraits;
}

- (BOOL)prefersStatusBarHidden {
    // Override to negate this documented special case, and avoid erratic hiding of status bar in conjunction with `traitCollection` override:
    // For apps linked against iOS 8 or later, this method returns true if the view controller is in a vertically compact environment.
    return NO;
}

Bu, iPad'e iPhone 7 Plus ile aynı boyut özelliklerini verir. Diğer iPhone modellerinin, yönden bağımsız olarak genellikle 'kompakt genişlik' özelliğine (normal genişlik yerine) sahip olduğunu unutmayın.

İPhone 7 Plus'ı bu şekilde taklit etmek, bu modelin koddaki özelleştirmelerden habersiz olan Xcode'un Arayüz Oluşturucusunda iPad için bir stand-in olarak kullanılmasına izin veriyor.

İPad'deki Bölünmüş Görünümün normal tam ekran işleminden farklı boyut özellikleri kullanabileceğini unutmayın.

Bu cevap, bazı iyileştirmelerle birlikte bu blog gönderisinde benimsenen yaklaşıma dayanmaktadır .

Güncelleme 2019-01-02: iPad manzarasında aralıklı olarak ortaya çıkan gizli durum çubuğunu düzeltmek için güncellendi ve UITraitCollection. Ayrıca, Apple belgelerinin gerçekten geçersiz kılmaya karşı tavsiyede bulunduğunu da belirtti traitCollection, bu nedenle gelecekte bu teknikle ilgili sorunlar ortaya çıkabilir.


Apple, traitCollectionmülkün salt okunur olduğunu belirtir : developer.apple.com/documentation/uikit/uitraitenvironment/…
cleverbit

4

RonDiamond'un uzun ve faydalı cevabı, ilkeleri anlamak için iyi bir başlangıç, ancak benim için çalışan kod (iOS 8+) geçersiz kılma yöntemine dayanıyor (UITraitCollection *)traitCollection

Bu nedenle, InterfaceBuilder'da Genişlik - Kompakt varyasyonlarıyla, örneğin kısıtın Yüklü özelliği için kısıtlamalar ekleyin. Yani Genişlik - Tümü yatay için, Genişlik - Dikey için Kompakt.

Geçerli görünüm denetleyicisi boyutuna göre koddaki kısıtlamaları değiştirmek için, aşağıdakileri UIViewController sınıfınıza eklemeniz yeterlidir:

- (UITraitCollection *)traitCollection
{
    UITraitCollection *verticalRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular];

    if (self.view.bounds.size.width < self.view.bounds.size.height) {
        // wCompact, hRegular
        return [UITraitCollection traitCollectionWithTraitsFromCollections:
                @[[UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact],
                  verticalRegular]];
    } else {
        // wRegular, hRegular
        return [UITraitCollection traitCollectionWithTraitsFromCollections:
                @[[UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular],
                  verticalRegular]];
    }
}

0

Manzara modunuz portre modunuzdan ne kadar farklı olacak? Çok farklıysa, başka bir görüntüleme denetleyicisi oluşturup cihaz yatay konumdayken yüklemek iyi bir fikir olabilir

Örneğin

    if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) 
    //load landscape view controller here

Evet bu bir seçenektir. Ama bunun en uygun olduğunu düşünmüyorum. Demek istediğim, ios8'de iphone için dikey ve yatay modlar için farklı boyutlandırma sınıfı özellikleri kullanma seçeneği varsa, o zaman neden iPad için aynı değil?
neelIVP

Xcode 6'dan önce, farklı yönelim için farklı storyboard kullanabiliriz. Çoğu görüntü denetleyicisi aynıysa bu çok verimli değildir. Ancak farklı düzenler için çok uygundur. Xcode 6'da bunu yapmanın bir yolu yoktur. Belki de farklı yönelim için farklı görünüm denetleyicisi oluşturmak tek çözümdür.
Bagusflyer

2
Her seferinde farklı viewController'ı yüklemek, özellikle ekranda bir şey oluyorsa çok verimsiz görünüyor. Aynı görünümü kullanmak ve otomatik düzen kısıtlamasını veya koddaki öğelerin konumunu değiştirmek çok daha iyidir. Veya yukarıda bahsedilen hack'i Apple bu sorunu çözene kadar kullanın.
Pahnev

0

Swift 5 Sürümü. İyi çalışıyor.

override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? {
    if UIScreen.main.bounds.width > UIScreen.main.bounds.height {
        let collections = [UITraitCollection(horizontalSizeClass: .regular),
                           UITraitCollection(verticalSizeClass: .compact)]
        return UITraitCollection(traitsFrom: collections)
    }
    return super.overrideTraitCollection(forChild: childViewController)
}

-3

@RonDiamond çözümü için Swift 3.0 kodu

class Test : UIViewController {


var _willTransitionToPortrait: Bool?
var _traitCollection_CompactRegular: UITraitCollection?
var _traitCollection_AnyAny: UITraitCollection?

func viewDidLoad() {
    super.viewDidLoad()
    self.upReferenceSizeClasses = null
}

func setUpReferenceSizeClasses() {
    var traitCollection_hCompact: UITraitCollection = UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClassCompact)
    var traitCollection_vRegular: UITraitCollection = UITraitCollection(verticalSizeClass: UIUserInterfaceSizeClassRegular)
    _traitCollection_CompactRegular = UITraitCollection(traitsFromCollections: [traitCollection_hCompact,traitCollection_vRegular])
    var traitCollection_hAny: UITraitCollection = UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClassUnspecified)
    var traitCollection_vAny: UITraitCollection = UITraitCollection(verticalSizeClass: UIUserInterfaceSizeClassUnspecified)
    _traitCollection_AnyAny = UITraitCollection(traitsFromCollections: [traitCollection_hAny,traitCollection_vAny])
}

func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    _willTransitionToPortrait = self.view.frame.size.height > self.view.frame.size.width
}

func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    _willTransitionToPortrait = size.height > size.width
}

func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection {
    var traitCollectionForOverride: UITraitCollection = _willTransitionToPortrait ? _traitCollection_CompactRegular : _traitCollection_AnyAny
    return traitCollectionForOverride
}}
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.