objective c - Classe de dimensionamento para os modos retrato e paisagem do iPad




objective-c ios8 (4)

Basicamente, quero ter minhas subvisões posicionadas de maneira diferente, dependendo da orientação do iPad (Retrato ou Paisagem) usando as Classes de dimensionamento apresentadas no xcode 6. Encontrei vários tutoriais explicando como diferentes classes de dimensionamento estão disponíveis para os Iphones em retrato e paisagem no IB mas, no entanto, parece não haver nenhum que cubra os modos paisagem ou retrato individuais para o iPad no IB. Alguém pode ajudar?


A resposta longa e útil de RonDiamond é um bom começo para compreender os princípios; no entanto, o código que funcionou para mim (iOS 8+) se baseia no método de substituição (UITraitCollection *)traitCollection

Portanto, adicione restrições no InterfaceBuilder com variações para Width - Compact, por exemplo, para a propriedade da restrição Installed. Então Width - Qualquer será válido para paisagem, Width - Compact para Portrait.

Para alternar restrições no código com base no tamanho atual do controlador de exibição, basta adicionar o seguinte à sua 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]];
    }
}

Código Swift 3.0 para a solução @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
}}

O iPad possui a característica de tamanho "regular" para as dimensões horizontal e vertical, não fazendo distinção entre retrato e paisagem.

Essas características de tamanho podem ser substituídas no código da subclasse UIViewController personalizado, por meio do método traitCollection , por exemplo:

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

Isso dá ao iPad as mesmas características de tamanho do iPhone 7 Plus. Observe que outros modelos do iPhone geralmente têm a característica 'largura compacta' (em vez da largura normal) independentemente da orientação.

A imitação do iPhone 7 Plus dessa maneira permite que esse modelo seja usado como substituto do iPad no Interface Builder do Xcode, que desconhece as personalizações no código.

Esteja ciente de que a exibição dividida no iPad pode usar diferentes características de tamanho da operação normal em tela cheia.

Esta resposta é baseada na abordagem adotada nesta postagem do blog , com algumas melhorias.

Atualização 2019-01-02: Atualizado para corrigir a barra de status oculta intermitente no cenário do iPad e o possível atropelamento de características (mais recentes) no UITraitCollection . Também observou que a documentação da Apple realmente recomenda a substituição do traitCollection , portanto, no futuro, poderá haver problemas com essa técnica.


Parece que a intenção da Apple é tratar as orientações do iPad da mesma forma - mas, como muitos de nós descobrimos, existem motivos de design muito legítimos para querer variar o layout da interface do usuário para o iPad Portrait vs. iPad Landscape.

Infelizmente, o sistema operacional atual parece não oferecer suporte para essa distinção ... o que significa que voltamos a manipular restrições de layout automático em código ou soluções alternativas para alcançar o que idealmente poderíamos obter gratuitamente usando o Adaptive UI .

Não é uma solução elegante.

Não existe uma maneira de aproveitar a mágica que a Apple já incorporou no IB e no UIKit para usar uma classe de tamanho de nossa escolha para uma determinada orientação?

~

Ao pensar sobre o problema de maneira mais genérica, percebi que 'classes de tamanho' são simplesmente maneiras de abordar vários layouts armazenados no IB, para que possam ser chamados conforme necessário em tempo de execução.

De fato, uma 'classe de tamanho' é realmente apenas um par de valores enum. No UIInterface.h:

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

Portanto, independentemente do que a Apple tenha decidido nomear essas diferentes variações, fundamentalmente, elas são apenas um par de números inteiros usados ​​como um identificador único de tipos, para distinguir um layout do outro, armazenado no IB.

Agora, supondo que criamos um layout alternativo (usando uma classe de tamanho não utilizada) no IB - digamos, para o iPad Portrait ... existe uma maneira de o dispositivo usar nossa escolha de classe de tamanho (layout da interface do usuário) conforme necessário em tempo de execução ?

Depois de tentar várias abordagens diferentes (menos elegantes) para o problema, suspeitei que poderia haver uma maneira de substituir a classe de tamanho padrão programaticamente. E existe (em 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);

Portanto, se você pode empacotar sua hierarquia de controlador de exibição como um controlador de exibição 'filho' e adicioná-la a um controlador de exibição pai de nível superior ... então você pode substituir condicionalmente o filho a pensar que é uma classe de tamanho diferente do padrão do sistema operacional.

Aqui está uma implementação de exemplo que faz isso, no controlador de exibição 'pai':

@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

Como uma demonstração rápida para ver se funcionou, adicionei etiquetas personalizadas especificamente às versões 'Regular / Regular' e 'Compact / Regular' do layout do controlador filho no IB:

E aqui está o que parece funcionar, quando o iPad está nas duas orientações:

Voila! Configurações de classe de tamanho personalizado em tempo de execução.

Espero que a Apple torne isso desnecessário na próxima versão do sistema operacional. Enquanto isso, essa pode ser uma abordagem mais elegante e escalável do que mexer programaticamente com restrições de layout automático ou fazer outras manipulações no código.

~

EDIT (4/6/15): Lembre-se de que o código de exemplo acima é essencialmente uma prova de conceito para demonstrar a técnica. Sinta-se à vontade para se adaptar conforme necessário para seu próprio aplicativo específico.

~

EDIT (24/7/15): É gratificante que a explicação acima pareça ajudar a desmistificar o problema. Embora eu não o tenha testado, o código de mohamede1945 [abaixo] parece uma otimização útil para fins práticos. Sinta-se livre para testá-lo e deixe-nos saber o que você pensa. (No interesse da integridade, deixarei o código de exemplo acima como está.)