Classe de dimensionnement pour les modes portrait et paysage de l'iPad

je veux fondamentalement avoir mes sous-vues positionnées différemment selon l'orientation de l'iPad (Portrait ou paysage) en utilisant des Classes de taille introduites dans xcode 6. J'ai trouvé de nombreux tutoriels expliquant comment différentes classes de dimensionnement sont disponibles pour les Iphones dans portrait et paysage sur L'IB, mais il semble qu'il n'y en ait aucun qui couvre les différents modes paysage ou portrait pour l'iPad sur L'IB. Quelqu'un peut-il aider?

94
demandé sur RonDiamond 2014-10-29 17:37:53

6 réponses

il semble être L'intention D'Apple de traiter les deux orientations iPad comme la même -- mais comme un certain nombre d'entre nous trouvent, Il ya des raisons de conception très légitime de vouloir varier la mise en page UI pour Portrait iPad vs Paysage iPad.

malheureusement, L'OS actuel ne semble pas soutenir cette distinction ... ce qui signifie que nous sommes de retour à la manipulation des contraintes d'auto-mise en page dans le code ou des solutions de contournement similaires pour atteindre ce que nous devrions idéalement être en mesure d'obtenir pour libre à l'aide de l'INTERFACE utilisateur Adaptative.

pas une solution élégante.

N'y a-t-il pas un moyen de tirer parti de la magie Qu'Apple a déjà intégrée dans IB et UIKit pour utiliser une classe de taille de notre choix pour une orientation donnée?

~

en pensant au problème de façon plus générale, j'ai réalisé que les 'classes de taille' sont simplement des moyens de traiter les mises en page multiples qui sont stockées dans IB, de sorte qu'ils peuvent être appelé comme nécessaires lors de l'exécution.

en fait, une "classe de taille" est juste une paire de valeurs enum. De UIInterface.h:

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

donc, indépendamment de ce Qu'Apple a décidé de nommer ces différentes variations, fondamentalement, ils sont juste une paire d'entiers utilisés comme un identifiant unique de sortes, pour distinguer un layout d'un autre, stocké dans IB.

maintenant, supposons que nous créons une alternative mise en page (en utilisant une classe de taille non utilisée) dans IB -- disons, Pour Portrait iPad ... y a-t-il un moyen pour que l'appareil utilise notre choix de la classe de taille (configuration de L'interface utilisateur) comme nécessaire à l'exécution?

après avoir essayé plusieurs approches différentes (moins élégantes) au problème, j'ai suspecté qu'il pourrait y avoir un moyen de surcharger la classe de taille par défaut programmatically. Et il y a (dans 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);

ainsi, si vous pouvez empaqueter votre vue la hiérarchie du contrôleur en tant que contrôleur de vue' enfant', et l'ajouter à un contrôleur de vue parent de niveau supérieur ... ensuite, vous pouvez conditionnellement surcharger l'enfant en pensant que c'est une classe de taille différente que la valeur par défaut de L'OS.

voici un exemple d'implémentation qui fait ceci, dans le contrôleur de vue' parent':

@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

comme démo rapide pour voir si cela a fonctionné, j'ai ajouté des étiquettes personnalisées spécifiquement au 'régulier / régulier' et Versions 'compactes / régulières' de la disposition du contrôleur pour enfants en IB:

enter image description here enter image description here

et voici ce que cela ressemble à courir, quand l'iPad est dans les deux orientations: enter image description here enter image description here

voilà! Configurations de classe de taille personnalisées à l'exécution.

avec un peu de chance Apple rendra cela inutile dans la prochaine version de L'OS. Dans entre-temps, cette approche est peut-être plus élégante et évolutive que de jouer programmatiquement avec les contraintes d'auto-mise en page ou de faire d'autres manipulations en code.

~

MODIFIER (6/4/15): s'il vous Plaît garder à l'esprit que l'exemple de code ci-dessus est essentiellement une preuve de concept pour montrer la technique. N'hésitez pas à vous adapter au besoin pour votre propre application.

~

EDIT (7/24/15): il est gratifiant que l'explication ci-dessus semble aider à démystifier la question. Bien que je ne l'ai pas testé, le code de mohamede1945 [ci-dessous] ressemble à une optimisation utile à des fins pratiques. N'hésitez pas à tester et laissez-nous savoir ce que vous en pensez. (Par souci d'exhaustivité, je vais laisser le code d'échantillon ci-dessus tel quel.)

171
répondu RonDiamond 2018-08-29 04:51:20

comme résumé de la très longue réponse de RonDiamond. Tout ce que vous devez faire est dans votre contrôleur de vue racine.

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

puis à storyborad utilisez la largeur compacte pour le Portrait et la largeur régulière pour le paysage.

41
répondu mohamede1945 2015-08-06 18:10:52

l'iPad a le caractère de taille "régulière" pour les dimensions horizontales et verticales, ne faisant aucune distinction entre portrait et paysage.

ces caractéristiques de taille peuvent être supplantées dans votre code de sous-classe personnalisé UIViewController , par la méthode traitCollection , par exemple:

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

cela donne à l'iPad les mêmes caractéristiques de taille que l'iPhone 7 Plus. Notez que les autres modèles iPhone ont généralement la caractéristique " largeur compacte "(plutôt que régulière) largeur) quelle que soit l'orientation.

imiter l'iPhone 7 Plus de cette manière permet d'utiliser ce modèle comme un stand-in pour l'iPad dans L'Interface Builder de Xcode, qui n'est pas au courant des personnalisations dans le code.

soyez conscient que Split View sur l'iPad peut utiliser des traits de taille différente de l'opération normale plein écran.

cette réponse est basée sur l'approche adoptée dans ce billet de blog , avec certains amélioration.

6
répondu jedwidz 2016-12-14 15:10:34

la réponse longue et utile de RonDiamond est un bon début pour comprendre les principes, cependant le code qui a fonctionné pour moi (iOS 8+) est basé sur la méthode prépondérante (UITraitCollection *)traitCollection

ainsi, ajouter des contraintes dans InterfaceBuilder avec des variations pour la largeur - Compact, par exemple pour la propriété de contrainte installé. Ainsi la largeur-N'importe quel sera valable pour le paysage, la largeur - Compact pour le Portrait.

pour passer des contraintes dans le code basé sur le courant voir la taille du contrôleur, il suffit d'ajouter ce qui suit dans votre classe 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]];
    }
}
3
répondu Jadamec 2017-06-12 18:36:55

en quoi votre mode paysage sera-t-il différent de votre mode portrait? Si son très différent, il pourrait être une bonne idée de créer un autre contrôleur de vue et de le charger lorsque l'appareil est dans le paysage

par exemple

    if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) 
    //load landscape view controller here
0
répondu MendyK 2014-10-29 15:04:59

Swift 3.0 code pour la solution @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
}}
-2
répondu Xeieshan 2017-05-08 11:21:46