Größenklasse für iPad-Hoch- und Querformat


97

Grundsätzlich möchte ich, dass meine Unteransichten je nach Ausrichtung des iPad (Hochformat oder Querformat) mithilfe der in xcode 6 eingeführten Größenklassen unterschiedlich positioniert werden. Ich habe zahlreiche Tutorials gefunden, in denen erläutert wird, wie verschiedene Größenklassen für Iphones im Hoch- und Querformat auf dem IB verfügbar sind Es scheint jedoch keine zu geben, die einzelne Quer- oder Hochformatmodi für das iPad auf IB abdecken. Kann jemand helfen?


Es scheint keine nicht programmatische Lösung zu geben, die für den Standardbildschirm benötigt würde
SwiftArchitect

Antworten:


174

Es scheint Apples Absicht zu sein, beide iPad-Ausrichtungen als gleich zu behandeln - aber wie einige von uns feststellen, gibt es sehr legitime Designgründe, das UI-Layout für iPad Portrait im Vergleich zu iPad Landscape variieren zu wollen.

Leider scheint das aktuelle Betriebssystem diese Unterscheidung nicht zu unterstützen ... was bedeutet, dass wir wieder die Einschränkungen des automatischen Layouts in Code oder ähnlichen Problemumgehungen bearbeiten, um das zu erreichen, was wir idealerweise mit der adaptiven Benutzeroberfläche kostenlos erhalten sollten .

Keine elegante Lösung.

Gibt es nicht eine Möglichkeit, die Magie, die Apple bereits in IB und UIKit eingebaut hat, zu nutzen, um eine Größenklasse unserer Wahl für eine bestimmte Ausrichtung zu verwenden?

~

Als ich allgemeiner über das Problem nachdachte, wurde mir klar, dass 'Größenklassen' einfach Möglichkeiten sind, mehrere in IB gespeicherte Layouts zu adressieren, damit sie zur Laufzeit nach Bedarf aufgerufen werden können.

Tatsächlich ist eine 'Größenklasse' nur ein Paar von Aufzählungswerten. Von UIInterface.h:

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

Also unabhängig davon , was Apple hat beschlossen, nennt diese verschiedenen Variationen, im Grunde sind sie nur ein Paar von ganzen Zahlen als eindeutige Kennung der Art verwendet, ein Layout von einem anderen zu unterscheiden, in IB gespeichert.

Angenommen, wir erstellen in IB ein alternatives Layout (unter Verwendung einer nicht verwendeten Größenklasse) - beispielsweise für iPad Portrait ... gibt es eine Möglichkeit, das Gerät zur Laufzeit nach Bedarf die von uns gewählte Größenklasse (UI-Layout) verwenden zu lassen ?

Nachdem ich verschiedene (weniger elegante) Ansätze für das Problem ausprobiert hatte, vermutete ich, dass es eine Möglichkeit gibt, die Standardgrößenklasse programmgesteuert zu überschreiben. Und es gibt (in 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);

Wenn Sie also Ihre View-Controller-Hierarchie als untergeordneten View-Controller verpacken und zu einem übergeordneten View-Controller der obersten Ebene hinzufügen können, können Sie das untergeordnete Element unter der Bedingung überschreiben, dass es sich um eine andere Größenklasse als die Standardgröße handelt vom Betriebssystem.

Hier ist eine Beispielimplementierung, die dies im übergeordneten Ansichtscontroller tut:

@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

Als schnelle Demo, um zu sehen, ob es funktioniert hat, habe ich benutzerdefinierte Beschriftungen speziell zu den Versionen 'Regular / Regular' und 'Compact / Regular' des untergeordneten Controller-Layouts in IB hinzugefügt:

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

Und so sieht es beim Laufen aus, wenn sich das iPad in beiden Richtungen befindet: Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

Voila! Benutzerdefinierte Größenklassenkonfigurationen zur Laufzeit.

Hoffentlich macht Apple dies in der nächsten Version des Betriebssystems unnötig. In der Zwischenzeit ist dies möglicherweise ein eleganterer und skalierbarer Ansatz als das programmgesteuerte Durcheinander mit Einschränkungen beim automatischen Layout oder andere Manipulationen im Code.

~

EDIT (6/4/15): Bitte beachten Sie, dass der obige Beispielcode im Wesentlichen ein Proof of Concept ist die Technik zu demonstrieren. Sie können sich jederzeit an Ihre spezifische Anwendung anpassen.

~

EDIT (24.07.15): Es ist erfreulich, dass die obige Erklärung dazu beiträgt, das Problem zu entmystifizieren. Obwohl ich es nicht getestet habe, sieht der Code von mohamede1945 [unten] aus praktischen Gründen wie eine hilfreiche Optimierung aus. Probieren Sie es einfach aus und teilen Sie uns Ihre Meinung mit. (Der Vollständigkeit halber lasse ich den obigen Beispielcode unverändert.)


1
Toller Beitrag @RonDiamond!
Amergin

3
Apple verfolgt einen ähnlichen Ansatz zur Neudefinition der Breitengrößenklasse in Safari, sodass Sie sicher sein können, dass dies ein mehr oder weniger unterstützter Ansatz ist. Sie sollten die zusätzlichen Ivars nicht wirklich brauchen; UITraitCollectionist ausreichend optimiert und overrideTraitCollectionForChildViewControllerwird selten genug aufgerufen, sodass es kein Problem sein sollte, die Breitenprüfung durchzuführen und sie dann zu erstellen.
Zwaldowski

1
@zwaldowski Danke. Der Beispielcode hier dient nur zur Veranschaulichung der Technik und kann natürlich auf verschiedene Arten nach Wunsch optimiert werden. (In der Regel halte ich es nicht für eine schlechte Idee, ein Objekt festzuhalten, wenn ein Objekt wiederholt verwendet wird [z. B. wenn das Gerät die Ausrichtung ändert], aber wie Sie hervorheben, der Leistungsunterschied hier kann minimal sein.)
RonDiamond

1
@Rashmi: Wie ich in der Erklärung angedeutet habe, spielt es letztendlich keine Rolle, welche Sie wählen - Sie ordnen die Layouts nur einem Paar von Aufzählungswerten zu. Sie können also wirklich jede "Größenklasse" verwenden, die für Sie sinnvoll ist. Ich würde nur sicherstellen, dass es nicht zu Konflikten mit einer anderen (legitimen) Größenklasse kommt, die standardmäßig irgendwo vererbt wird.
RonDiamond

1
Was den Child View Controller betrifft, denke ich nicht, dass es wichtig ist. Sie sollten in der Lage sein, es programmgesteuert oder von einer Schreibfeder aus zu instanziieren, sofern es dem Container ordnungsgemäß als untergeordneter Controller hinzugefügt wurde.
RonDiamond

41

Als Zusammenfassung der sehr langen Antwort von RonDiamond. Alles, was Sie tun müssen, ist in Ihrem Root View Controller.

Ziel c

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

Schnell:

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

Verwenden Sie dann in Storyborad die kompakte Breite für Hochformat und die reguläre Breite für Querformat.


Ich frage mich, wie das mit den neuen Split-Screen-Modi funktionieren wird ... eine Möglichkeit, dies herauszufinden!
mm2001

UITraitCollection ist nur für iOS 8.0+
mohamede1945

Funktioniert bei mir nicht Ich habe eine Ansicht mit zwei Containerviews, die je nach Ausrichtung an einer anderen Stelle angezeigt werden müssen. Ich habe alle benötigten Größenklassen erstellt, alles funktioniert für das iPhone. Ich habe versucht, Ihren Code zu verwenden, um auch die iPad-Ausrichtung zu trennen. Es wird jedoch nur seltsam auf die Ansichten wirken. Es ändert es nicht so, wie es soll. Irgendeine Ahnung, was los sein könnte?
NoSixties

1
Das ist für mich gearbeitet , wenn ich overrode - (UITraitCollection *)traitCollectionstatt overrideTraitCollectionForChildViewController. Auch die Einschränkungen sollten mit den Merkmalssammlungen übereinstimmen, also wC (hAny).
H. de Jonge

1
@ Apollo Ich würde, aber es würde nicht die eigentliche Frage beantworten. Werfen Sie einen Blick hier für die Apple-Beispiel, wie setOverrideTraitCollection verwendet github.com/ios8/AdaptivePhotosAnAdaptiveApplication/blob/master/...
malhal

5

Das iPad hat die Eigenschaft "normale" Größe sowohl für horizontale als auch für vertikale Dimensionen, wobei kein Unterschied zwischen Hoch- und Querformat besteht.

Diese Größenmerkmale können in Ihrem benutzerdefinierten UIViewControllerUnterklassencode über folgende Methode überschrieben werden traitCollection:

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

Dies gibt dem iPad die gleichen Größenmerkmale wie dem iPhone 7 Plus. Beachten Sie, dass andere iPhone-Modelle unabhängig von ihrer Ausrichtung im Allgemeinen die Eigenschaft "kompakte Breite" (anstelle der regulären Breite) aufweisen.

Durch die Nachahmung des iPhone 7 Plus auf diese Weise kann dieses Modell als Ersatz für das iPad im Interface Builder von Xcode verwendet werden, bei dem keine Anpassungen im Code bekannt sind.

Beachten Sie, dass die geteilte Ansicht auf dem iPad möglicherweise andere Größenmerkmale als der normale Vollbildbetrieb verwendet.

Diese Antwort basiert auf dem in diesem Blogbeitrag verfolgten Ansatz mit einigen Verbesserungen.

Update 2019-01-02: Aktualisiert, um die zeitweise verborgene Statusleiste in der iPad-Landschaft und das mögliche Trampeln von (neueren) Merkmalen in zu beheben UITraitCollection. Es wird auch darauf hingewiesen, dass in der Apple-Dokumentation tatsächlich davon abgeraten wird, diese zu überschreiben traitCollection, sodass sich in Zukunft möglicherweise Probleme mit dieser Technik herausstellen.


Apple gibt an, dass die traitCollectionEigenschaft schreibgeschützt ist: developer.apple.com/documentation/uikit/uitraitenvironment/…
cleverbit

4

Die lange und hilfreiche Antwort von RonDiamond ist ein guter Anfang, um die Prinzipien zu verstehen. Der Code, der für mich (iOS 8+) funktioniert hat, basiert jedoch auf einer übergeordneten Methode (UITraitCollection *)traitCollection

Fügen Sie also in InterfaceBuilder Einschränkungen mit Variationen für Breite - Kompakt hinzu, z. B. für die Eigenschaft Installiert der Einschränkung. Also Breite - Jeder gilt für Landschaft, Breite - Kompakt für Porträt.

Um Einschränkungen im Code basierend auf der aktuellen Größe des View Controllers zu ändern, fügen Sie Ihrer UIViewController-Klasse einfach Folgendes hinzu:

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

Wie viel anders wird Ihr Querformatmodus sein als Ihr Hochformatmodus? Wenn es sehr unterschiedlich ist, ist es möglicherweise eine gute Idee, einen anderen Ansichts-Controller zu erstellen und ihn zu laden, wenn sich das Gerät im Querformat befindet

Beispielsweise

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

Ja, das ist eine Option. Aber ich denke nicht, dass es das optimalste ist. Mein Punkt ist, dass wenn es eine Option gibt, unterschiedliche Größenklassenmerkmale für Hoch- und Querformat für das iPhone in ios8 zu verwenden, warum dann nicht dasselbe für das iPad?
neelIVP

Vor Xcode 6 können wir verschiedene Storyboards für unterschiedliche Orientierungen verwenden. Das ist nicht sehr effizient, wenn die meisten View Controller gleich sind. Aber es ist sehr praktisch für verschiedene Layouts. In Xcode 6 gibt es keine Möglichkeit dazu. Möglicherweise ist die Erstellung einer anderen Ansichtssteuerung für eine andere Ausrichtung die einzige Lösung.
Bagusflyer

2
Das Laden eines anderen viewControllers jedes Mal scheint sehr ineffizient zu sein, insbesondere wenn auf dem Bildschirm etwas passiert. Es ist viel besser, dieselbe Ansicht zu verwenden und entweder die Autolayout-Einschränkung oder die Position der Elemente im Code zu ändern. Oder verwenden Sie den oben genannten Hack, bis Apple dieses Problem behoben hat.
Pahnev

0

Swift 5 Version. Es funktioniert gut.

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

Schneller 3.0-Code für die @ RonDiamond-Lösung

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
}}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.