2014-10-29 10 views
89

Grundsätzlich möchte ich meine Subviews abhängig von der Ausrichtung des iPads (Hochformat oder Querformat) mit Größenklassen in xcode 6 positioniert haben. Ich habe zahlreiche Tutorials gefunden, die verschiedene Größenklassen erklären sind für Iphones im Hoch- und Querformat auf dem IB verfügbar, aber es scheint keine zu geben, die einzelne Landscape- oder Portrait-Modi für das iPad auf IB abdecken. Kann jemand helfen?Größenklasse für iPad Hochformat und Landschaftsmodi

+0

Es scheint keine nicht-programmatische Lösung zu sein, die für den Standard benötigt würden Bildschirm – SwiftArchitect

Antwort

0

Wie viel anders ist Ihr Landscape-Modus als Ihr Portrait-Modus? Wenn es ganz anders, es könnte eine gute Idee sein, einen anderen View-Controller zu erstellen und laden, wenn das Gerät im Querformat ist

Zum Beispiel

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

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

+0

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

+2

Loading verschiedene ViewController jedes Mal scheint sehr ineffizient, vor allem, wenn etwas auf dem Bildschirm passiert ist. Es ist viel besser, dieselbe Ansicht zu verwenden und entweder die automatische Layoutbedingung oder die Position der Elemente im Code zu manipulieren. Oder um den oben erwähnten Hack zu verwenden, bis Apple dieses Problem anspricht. – Pahnev

168

Es scheint Apples Absicht zu sein, beide iPad Orientierungen zu behandeln, als Das Gleiche - aber wie wir feststellen müssen, gibt es sehr legitime Design-Gründe, das UI-Layout für iPad Portrait vs. iPad Landscape zu variieren.

Leider scheint das aktuelle Betriebssystem keine Unterstützung für diese Unterscheidung zu bieten ... was bedeutet, dass wir wieder Auto-Layout-Einschränkungen in Code oder ähnlichen Problemumgehungen manipulieren, um das zu erreichen, was wir im Idealfall erreichen können kostenlos mit Adaptive UI.

Keine elegante Lösung.

Gibt es keine 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 Orientierung zu verwenden?

~

Beim Nachdenken über das Problem allgemeinen, wurde mir klar, dass ‚Größenklassen‘ sind einfach Möglichkeiten, mehrere Layouts zu adressieren, die in IB gespeichert werden, so dass sie zur Laufzeit benötigt aufgerufen werden können.

In der Tat ist eine "Größenklasse" wirklich nur ein Paar Enum-Werte. 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 Namen diese verschiedenen Varianten entschieden hat, im Grunde, sie sind nur ein Paar von ganzen Zahlen als eindeutige Kennung der Art verwendet, zu unterscheiden, ein Layout aus ein anderes, in IB gespeichert.

Jetzt, unter der Annahme, dass wir ein alternatives Layout (unter Verwendung einer nicht genutzten Größenklasse) in IB erstellen - sagt sie, für iPad Portrait ... ist es eine Möglichkeit, das Gerät verwendet unsere Wahl der Größenklasse haben (UI Layout) nach Bedarf zur Laufzeit?

Nachdem ich mehrere verschiedene (weniger elegante) Ansätze für das Problem versucht habe, 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 'Kind' View-Controller verpacken können, und fügen Sie es zu einem Top-Level-Eltern-View-Controller ... dann Sie können das untergeordnete Element überschreiben, indem Sie davon ausgehen, dass es sich um eine andere Größenklasse als die Standardeinstellung des Betriebssystems handelt.

Hier ist eine Beispielimplementierung, das dies tut, in der ‚Eltern‘ View-Controller:

@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 { 
    _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, habe ich benutzerdefinierte Etiketten speziell auf die ‚Regular/Regular‘ und 'Compact/Regular' -Versionen des Kindes Controller-Layout in IB:

enter image description here enter image description here

Und hier ist, wie es aussieht, wird nach Drücken der iPad in beiden Orientierungen ist: enter image description here enter image description here

Voila! Benutzerdefinierte Größenklassenkonfigurationen zur Laufzeit

Hoffentlich wird Apple dies in der nächsten Version des Betriebssystems unnötig machen. In der Zwischenzeit könnte dies ein eleganterer und skalierbarerer Ansatz sein, als programmgesteuert mit Auto-Layout-Einschränkungen zu arbeiten oder andere Manipulationen im Code durchzuführen.

~

EDIT (6/4/15): Bitte beachten Sie, dass der obige Beispielcode ist im Wesentlichen ein Proof of Concept, die Technik zu demonstrieren. Fühlen Sie sich frei, sich für Ihre eigene spezifische Anwendung anzupassen.

~

EDIT (7/24/15): Es ist erfreulich, dass die obige Erklärung scheint das Problem zu helfen entmystifizieren. Während ich es nicht getestet habe, sieht der Code von mohamede1945 [unten] wie eine hilfreiche Optimierung für praktische Zwecke aus. Fühlen Sie sich frei, es auszuprobieren und lassen Sie uns wissen, was Sie denken. (Im Interesse der Vollständigkeit belasse ich den obigen Beispielcode unverändert.)

+0

Großer Beitrag @RonDiamond! – amergin

+3

Apple verwendet einen ähnlichen Ansatz zur Neudefinition der Breitenklassenklasse 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; 'UITraitCollection' ist ausreichend optimiert und' overrideTraitCollectionForChildViewController' wird selten genug aufgerufen, so dass es kein Problem sein sollte, die Breitenprüfung durchzuführen und sie dann zu erstellen. – zwaldowski

+1

@zwaldowski Danke. Der Beispielcode dient nur dazu, die Technik zu demonstrieren, und kann offensichtlich auf verschiedene Arten wie gewünscht optimiert werden. (In der Regel, wenn ein Objekt immer wieder und wieder benutzt wird [zB wenn das Gerät seine Ausrichtung ändert], halte ich es nicht für eine schlechte Idee, ein Objekt festzuhalten, aber wie Sie darauf hinweisen, ist der Leistungsunterschied hier kann minimal sein.) – RonDiamond

39

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

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

Dann in storyborad Baubreite für Portrait verwenden und regelmäßige Breite für Landschaft.

+0

Funktioniert das für iOS7? – Zammbi

+0

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

+0

funktioniert nicht für iOS7.1 –

4

Das iPad hat die "normale" Größeneigenschaft für horizontale und vertikale Abmessungen, wobei nicht zwischen Hoch- und Querformat unterschieden wird.

können diese Größe Züge in Ihrer benutzerdefinierten UIViewController Unterklasse Code außer Kraft gesetzt werden, über Verfahren traitCollection, zum Beispiel:

- (UITraitCollection *)traitCollection { 
    // Distinguish portrait and landscape size traits for iPad, similar to iPhone 7 Plus. 
    UITraitCollection *superTraits = [super traitCollection]; 
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { 
     UITraitCollection *horizontalRegular = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular]; 
     UITraitCollection *horizontalCompact = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact]; 
     UITraitCollection *verticalRegular = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassRegular]; 
     UITraitCollection *verticalCompact = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact]; 

     UITraitCollection *regular = [UITraitCollection traitCollectionWithTraitsFromCollections:@[horizontalRegular, verticalRegular]]; 
     UITraitCollection *portrait = [UITraitCollection traitCollectionWithTraitsFromCollections:@[horizontalCompact, verticalRegular]]; 
     UITraitCollection *landscape = [UITraitCollection traitCollectionWithTraitsFromCollections:@[horizontalRegular, verticalCompact]]; 

     if ([superTraits containsTraitsInCollection:regular]) { 
      if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation])) 
       return portrait; 
      else 
       return landscape; 
     } 
    } 
    return superTraits; 
} 

Das das iPad die gleiche Größe Eigenschaften wie das iPhone 7 Plus bietet. Beachten Sie, dass andere iPhone-Modelle unabhängig von ihrer Ausrichtung im Allgemeinen die Eigenschaft "kompakte Breite" (statt der normalen Breite) aufweisen.

Durch die Nachahmung des iPhone 7 Plus kann dieses Modell als Stand-in für das iPad im Xcode Interface Builder verwendet werden, der keine Anpassungen im Code kennt.

Beachten Sie, dass die Split-Ansicht auf dem iPad beim normalen Vollbildbetrieb möglicherweise andere Größenmerkmale verwendet.

Diese Antwort basiert auf dem Ansatz this blog post, mit einigen Verbesserungen.

-2

Swift 3.0-Code für @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 
}} 
+0

Dieser Code würde nie kompilieren –

2

Die lange und hilfreiche Antwort von RonDiamond ist ein guter Anfang, die Prinzipien, aber der Code zu verstehen, die für mich gearbeitet (iOS 8+) basiert auf zwingende Methode (UITraitCollection *)traitCollection

Fügen Sie daher Einschränkungen in InterfaceBuilder mit Variationen für Width - Compact hinzu, z. B. für die Eigenschaft constraint Installiert. So wird Breite - Alle für Landschaft, Breite - Kompakt für Porträt gültig sein.

Um Einschränkungen im Code-Schaltern auf dem gegenwärtigen View-Controller Größe, fügen Sie einfach den folgend in Ihre UIViewController Klasse:

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