Klasa rozmiarów dla iPada w trybie pionowym i poziomym

97

Zasadniczo chcę, aby moje podglądy były ustawione inaczej w zależności od orientacji iPada (pionowa lub pozioma) przy użyciu klas rozmiaru wprowadzonych w xcode 6. Znalazłem wiele samouczków wyjaśniających, jak różne klasy rozmiaru są dostępne dla iPhone'ów w orientacji pionowej i poziomej na IB ale wydaje się, że nie ma żadnego, który obejmowałby indywidualne tryby krajobrazu lub portretu dla iPada na IB. Czy ktoś może pomóc?

neelIVP
źródło
Wygląda na to, że nie ma nieprogramowego rozwiązania, które byłoby potrzebne na ekranie domyślnym
SwiftArchitect

Odpowiedzi:

174

Wygląda na to, że intencją Apple jest traktowanie obu orientacji iPada jako tej samej - ale jak wielu z nas odkrywa, istnieją bardzo uzasadnione powody projektowe, aby chcieć zmienić układ interfejsu użytkownika dla iPad Portrait i iPad Landscape.

Niestety, wydaje się, że obecny system operacyjny nie zapewnia wsparcia dla tego rozróżnienia ... co oznacza, że ​​wracamy do manipulowania ograniczeniami automatycznego układu w kodzie lub podobnych obejść, aby osiągnąć to, co powinniśmy uzyskać za darmo za pomocą Adaptive UI .

Nie jest to eleganckie rozwiązanie.

Nie jest jakiś sposób wykorzystać magię, że Apple jest już wbudowany w IB i UIKit użyć klasy wielkości naszego wyboru dla danej orientacji?

~

Myśląc o problemie bardziej ogólnie, zdałem sobie sprawę, że „klasy rozmiarów” to po prostu sposoby rozwiązania wielu układów przechowywanych w IB, tak aby można je było wywoływać w razie potrzeby w czasie wykonywania.

W rzeczywistości „klasa wielkości” to tak naprawdę tylko para wartości wyliczeniowych. Z UIInterface.h:

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

Więc niezależnie od tego, co Apple zdecydowało się nazwać te różne odmiany, zasadniczo są one tylko parą liczb całkowitych używanych jako swoisty unikalny identyfikator, aby odróżnić jeden układ od drugiego, przechowywany w IB.

Teraz, zakładając, że utworzymy alternatywny układ (używając nieużywanej klasy rozmiaru) w IB - powiedzmy, dla iPad Portrait ... czy istnieje sposób, aby urządzenie użyło naszej wybranej klasy rozmiaru (układu interfejsu użytkownika) w razie potrzeby w czasie wykonywania ?

Po wypróbowaniu kilku różnych (mniej eleganckich) podejść do problemu, podejrzewałem, że może istnieć sposób na programowe zastąpienie domyślnej klasy rozmiaru. I jest (w UIViewController.h):

// 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);

Tak więc, jeśli możesz spakować swoją hierarchię kontrolera widoku jako `` podrzędny '' kontroler widoku i dodać go do nadrzędnego kontrolera widoku najwyższego poziomu ... wtedy możesz warunkowo zastąpić dziecko myśląc, że jest to inna klasa rozmiaru niż domyślna z systemu operacyjnego.

Oto przykładowa implementacja, która to robi w kontrolerze widoku „nadrzędnego”:

@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

Aby szybko sprawdzić, czy to zadziałało, dodałem niestandardowe etykiety specjalnie do wersji „Regular / Regular” i „Compact / Regular” układu kontrolera podrzędnego w IB:

wprowadź opis obrazu tutaj wprowadź opis obrazu tutaj

A oto jak wygląda bieganie, gdy iPad jest w obu orientacjach: wprowadź opis obrazu tutaj wprowadź opis obrazu tutaj

Voila! Konfiguracje klas rozmiaru niestandardowego w czasie wykonywania.

Miejmy nadzieję, że Apple uczyni to niepotrzebnym w następnej wersji systemu operacyjnego. W międzyczasie może to być bardziej eleganckie i skalowalne podejście niż programowe manipulowanie ograniczeniami automatycznego układu lub wykonywanie innych operacji w kodzie.

~

EDYCJA (6/4/15): Należy pamiętać, że powyższy przykładowy kod jest zasadniczo dowodem słuszności koncepcji w celu zademonstrowania techniki. Możesz dowolnie dostosowywać się do własnych potrzeb.

~

EDYCJA (24.07.15): To satysfakcjonujące, że powyższe wyjaśnienie wydaje się pomóc w wyjaśnieniu problemu. Chociaż tego nie testowałem, kod mohamede1945 [poniżej] wygląda na przydatną optymalizację do celów praktycznych. Zapraszam do przetestowania i daj nam znać, co myślisz. (Ze względu na kompletność pozostawię powyższy przykładowy kod bez zmian).

RonDiamond
źródło
1
Świetny post @RonDiamond!
amergin
3
Apple stosuje podobne podejście do redefiniowania klasy rozmiaru szerokości w Safari, więc możesz mieć pewność, że jest to mniej lub bardziej obsługiwane podejście. Naprawdę nie powinieneś potrzebować dodatkowych blaszek; UITraitCollectionjest wystarczająco zoptymalizowany i overrideTraitCollectionForChildViewControllerjest wywoływany na tyle rzadko, że sprawdzenie szerokości i utworzenie go nie powinno stanowić problemu.
zwaldowski
1
@zwaldowski Thanks. Przykładowy kod służy tylko do zademonstrowania techniki i oczywiście można go zoptymalizować na różne sposoby, zgodnie z potrzebami. (Zgodnie z ogólną zasadą, gdy obiekt jest używany wielokrotnie [np. Za każdym razem, gdy urządzenie zmienia orientację], nie sądzę, aby trzymanie obiektu było złym pomysłem; ale jak zauważyłeś, różnica w wydajności tutaj może być minimalne.)
RonDiamond
1
@Rashmi: Jak wspomniałem w wyjaśnieniu, ostatecznie nie ma znaczenia, który wybierzesz - po prostu mapujesz układ (y) na parę wartości wyliczeniowych. Możesz więc naprawdę użyć dowolnej „klasy rozmiaru”, która ma dla Ciebie sens. Po prostu upewnię się, że nie koliduje z jakąś inną (legalną) klasą wielkości, która może być gdzieś dziedziczona jako domyślna.
RonDiamond
1
Jeśli chodzi o kontroler widoku dziecka, nie sądzę, że ma to znaczenie. Powinieneś być w stanie utworzyć jego wystąpienie programowo lub z nib, o ile jest poprawnie dodany jako kontroler podrzędny do kontenera.
RonDiamond
41

Podsumowując bardzo długą odpowiedź RonDiamonda. Wszystko, co musisz zrobić, to w głównym kontrolerze widoku.

Cel C

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

Szybki:

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

Następnie w storyborad użyj małej szerokości dla opcji Pionowej i Normalnej szerokości dla Poziomu.

mohamede1945
źródło
Zastanawiam się, jak to się potoczy z nowymi trybami podzielonego ekranu ... jednym ze sposobów, aby się dowiedzieć!
mm2001,
UITraitCollection jest dostępne tylko na iOS 8.0+
mohamede1945
Nie działa dla mnie. Mam widok z dwoma wyświetleniami zawierającymi, które muszą być wyświetlane w innym miejscu w zależności od orientacji. Stworzyłem wszystkie potrzebne klasy rozmiaru, wszystko działa na iPhone'a. Próbowałem użyć twojego kodu, aby również oddzielić orientację iPada. Jednak będzie to po prostu działało dziwnie na widokach. Nie zmienia tego tak, jak powinien. Masz jakąś wskazówkę, co się dzieje?
NoSixties
1
To zadziałało dla mnie, kiedy przegrałem - (UITraitCollection *)traitCollectionzamiast overrideTraitCollectionForChildViewController. Również ograniczenia powinny pasować do kolekcji traitcoll, czyli wC (hAny).
H. de Jonge
1
@Apollo Chciałbym, ale nie odpowiedziałbym na rzeczywiste pytanie. Spójrz tutaj na próbkę Apple dotyczącą korzystania z setOverrideTraitCollection github.com/ios8/AdaptivePhotosAnAdaptiveApplication/blob/master/ ...
malhal
5

IPad ma cechę „regularnego” rozmiaru zarówno w przypadku wymiarów poziomych, jak i pionowych, bez rozróżnienia między portretem a krajobrazem.

Te cechy rozmiaru można przesłonić w UIViewControllerkodzie niestandardowej podklasy za pomocą metody traitCollection, na przykład:

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

Dzięki temu iPad ma takie same cechy rozmiaru jak iPhone 7 Plus. Zwróć uwagę, że inne modele iPhone'a generalnie mają cechę „niewielkiej szerokości” (zamiast zwykłej szerokości), niezależnie od orientacji.

Naśladowanie iPhone'a 7 Plus w ten sposób pozwala na użycie tego modelu jako stand-in dla iPada w Interface Builder Xcode, który nie jest świadomy dostosowań w kodzie.

Należy pamiętać, że Split View na iPadzie może mieć inne cechy rozmiaru niż normalne działanie na pełnym ekranie.

Ta odpowiedź jest oparta na podejściu przyjętym w tym poście na blogu , z pewnymi ulepszeniami.

Aktualizacja 2019-01-02: Zaktualizowano, aby naprawić przerywany ukryty pasek stanu w krajobrazie iPada i potencjalne deptanie (nowszych) cech w UITraitCollection. Zauważyłem również, że dokumentacja Apple faktycznie odradza zastępowanie traitCollection, więc w przyszłości mogą pojawić się problemy z tą techniką.

jedwidz
źródło
Apple określa, że traitCollectionwłaściwość ma być tylko do odczytu: developer.apple.com/documentation/uikit/uitraitenvironment/…
cleverbit
4

Długa i pomocna odpowiedź RonDiamond to dobry początek zrozumienia zasad, jednak kod, który u mnie zadziałał (iOS 8+) oparty jest na metodzie nadrzędnej (UITraitCollection *)traitCollection

Dlatego Dodaj ograniczenia w InterfaceBuilder z odmianami Width - Compact, na przykład dla właściwości ograniczenia Installed. Tak więc Szerokość - dowolna będzie obowiązywać w przypadku poziomego, a Szerokość - kompaktowa w przypadku orientacji pionowej.

Aby przełączyć ograniczenia w kodzie na podstawie bieżącego rozmiaru kontrolera widoku, po prostu dodaj następujące elementy do swojej klasy UIViewController:

- (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]];
    }
}
Jadamec
źródło
0

Jak bardzo różni się twój tryb poziomy od trybu portretowego? Jeśli jest bardzo inny, dobrym pomysłem może być utworzenie innego kontrolera widoku i załadowanie go, gdy urządzenie jest w trybie poziomym

Na przykład

    if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) 
    //load landscape view controller here
MendyK
źródło
Tak, to jedna opcja. Ale nie sądzę, że jest to najbardziej optymalny. Chodzi mi o to, że jeśli istnieje opcja użycia różnych cech klas rozmiaru dla trybów portretowych i poziomych dla iPhone'a w ios8, to dlaczego nie to samo dla iPada?
neelIVP
Przed Xcode 6 możemy używać różnych scenorysów dla różnych orientacji. Nie jest to zbyt wydajne, jeśli większość kontrolerów widoku jest taka sama. Ale jest to bardzo wygodne dla różnych układów. W Xcode 6 nie ma takiej możliwości. Może jedynym rozwiązaniem jest utworzenie innego kontrolera widoku dla różnych orientacji.
Bagusflyer
2
Ładowanie różnych viewController za każdym razem wydaje się bardzo nieefektywne, szczególnie jeśli coś się dzieje na ekranie. O wiele lepiej jest używać tego samego widoku i albo manipulować ograniczeniem autoukładu, albo pozycją elementów w kodzie. Lub skorzystaj z powyższego hacka, dopóki Apple nie rozwiąże tego problemu.
Pahnev
0

Wersja Swift 5. To działa dobrze.

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)
}
Li Jin
źródło
-3

Kod Swift 3.0 dla rozwiązania @RonDiamond

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
}}
Zeeshan
źródło