Como obter altura e largura da tela dependentes da orientação?

96

Estou tentando determinar programaticamente a altura e a largura atuais do meu aplicativo. Eu uso isso:

CGRect screenRect = [[UIScreen mainScreen] bounds];

Mas isso resulta em uma largura de 320 e uma altura de 480, independentemente de o dispositivo estar na orientação retrato ou paisagem. Como posso determinar a largura e a altura atuais (ou seja, dependendo da orientação do dispositivo) da minha tela principal?

MusiGenesis
fonte

Respostas:

164

Você pode usar algo como UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation)determinar a orientação e então usar as dimensões de acordo.

NO ENTANTO, durante uma mudança de orientação como no UIViewController

- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation 
                                 duration:(NSTimeInterval)duration

Use a orientação passada toInterfaceOrientationuma vez que statusBarOrientation do UIApplication ainda apontará para a orientação antiga, pois ainda não mudou (já que você está dentro de um willmanipulador de eventos).

Resumo

Existem várias postagens relacionadas a isso, mas cada uma delas parece indicar que você deve:

  1. Observe [[UIScreen mainScreen] bounds]para obter as dimensões,
  2. Verifique em qual orientação você está e
  3. Considere a altura da barra de status (se exibida)

Links

Código de Trabalho

Normalmente não vou tão longe, mas você despertou meu interesse. O código a seguir deve resolver o problema. Eu escrevi uma categoria sobre UIApplication. Eu adicionei métodos de classe para obter o currentSize ou o tamanho em uma determinada orientação, que é o que você chamaria no UIViewController willRotateToInterfaceOrientation:duration:.

@interface UIApplication (AppDimensions)
+(CGSize) currentSize;
+(CGSize) sizeInOrientation:(UIInterfaceOrientation)orientation;
@end

@implementation UIApplication (AppDimensions)

+(CGSize) currentSize
{
    return [UIApplication sizeInOrientation:[UIApplication sharedApplication].statusBarOrientation];
}

+(CGSize) sizeInOrientation:(UIInterfaceOrientation)orientation
{
    CGSize size = [UIScreen mainScreen].bounds.size;
    UIApplication *application = [UIApplication sharedApplication];
    if (UIInterfaceOrientationIsLandscape(orientation))
    {
        size = CGSizeMake(size.height, size.width);
    }
    if (application.statusBarHidden == NO)
    {
        size.height -= MIN(application.statusBarFrame.size.width, application.statusBarFrame.size.height);
    }
    return size;
}

@end

Para usar o código de chamada simples [UIApplication currentSize]. Além disso, executei o código acima, então sei que funciona e relata as respostas corretas em todas as orientações. Observe que eu levo em consideração a barra de status. Curiosamente, tive que subtrair o MIN da altura e largura da barra de status.

Espero que isto ajude. : D

Outros pensamentos

Você poderia obter as dimensões observando a rootViewControllerpropriedade UIWindow . Eu olhei para isso no passado e ele relata da mesma forma as mesmas dimensões em retrato e paisagem, exceto que relata ter uma transformação de rotação:

(gdb) po [[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]

<UILayoutContainerView: 0xf7296f0; frame = (0 0; 320 480); transformar = [0, -1, 1, 0, 0, 0]; redimensionar automaticamente = W + H; camada = <CALayer: 0xf729b80 >>

(gdb) po [[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]

<UILayoutContainerView: 0xf7296f0; frame = (0 0; 320 480); redimensionar automaticamente = W + H; camada = <CALayer: 0xf729b80 >>

Não tenho certeza de como seu aplicativo funciona, mas se você não estiver usando um controlador de navegação de algum tipo, você poderia ter um UIView sob sua visualização principal com a altura / largura máxima do pai e aumenta / diminui com o pai. Então você poderia fazer: [[[[[[[UIApplication sharedApplication] keyWindow] rootViewController] view] subviews] objectAtIndex:0] frame]. Isso parece muito intenso em uma linha, mas essa é a ideia.

No entanto ... Ainda seria melhor seguir as 3 etapas acima no resumo. Comece a mexer com o UIWindows e você descobrirá coisas estranhas, como mostrar um UIAlertView mudará a keywindow do UIApplication para apontar para uma nova UIWindow que o UIAlertView criou. Quem sabia? Eu fiz isso depois de encontrar um bug em keyWindow e descobrir que ele mudou assim!

Sam
fonte
5
Parece uma tarefa tão elementar que estou tendo dificuldade em acreditar que preciso hackear meu próprio código para determinar algo assim.
MusiGenesis
2
Vou tentar outra solução e repassar se funcionar. Este site é tão agressivo se você não responder rapidamente, você pode muito bem não responder. haha ...: D Espero que minha resposta seja pelo menos útil. Novamente, vou tentar outra coisa bem rápido.
Sam,
1
Ótimo! (Aliás, um deles despertou o interesse) :-)
Vamos
1
Se você usar em applicationFramevez de bounds(em UIScreen), não será necessário subtrair a altura da barra de status.
aopsfan
2
stackoverflow.com/questions/24150359/… mainScreen().bounds.size tornou-se dependente da orientação do iOS 8 em diante
Gerald
39

Este é o meu código de solução! Este método pode adicionar ao Categroy da classe NSObject, ou você pode definir uma classe UIViewController personalizada Top e deixar todos os outros UIViewControllers herdá-la.

-(CGRect)currentScreenBoundsDependOnOrientation
{  

    CGRect screenBounds = [UIScreen mainScreen].bounds ;
    CGFloat width = CGRectGetWidth(screenBounds)  ;
    CGFloat height = CGRectGetHeight(screenBounds) ;
    UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;

    if(UIInterfaceOrientationIsPortrait(interfaceOrientation)){
        screenBounds.size = CGSizeMake(width, height);
    }else if(UIInterfaceOrientationIsLandscape(interfaceOrientation)){
        screenBounds.size = CGSizeMake(height, width);
    }
    return screenBounds ;
}

Observe , após o IOS8, como o Documento da Apple da propriedade bounds de UIScreen diz:

Discussão

Este retângulo é especificado no espaço de coordenadas atual, que leva em consideração quaisquer rotações de interface em vigor para o dispositivo. Portanto, o valor desta propriedade pode mudar quando o dispositivo gira entre as orientações retrato e paisagem.

portanto, para consideração de compatibilidade, devemos detectar a versão do IOS e fazer a alteração conforme abaixo:

#define IsIOS8 (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1)

-(CGRect)currentScreenBoundsDependOnOrientation
{  

    CGRect screenBounds = [UIScreen mainScreen].bounds ;
    if(IsIOS8){
        return screenBounds ;
    }
    CGFloat width = CGRectGetWidth(screenBounds)  ;
    CGFloat height = CGRectGetHeight(screenBounds) ;
    UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;

    if(UIInterfaceOrientationIsPortrait(interfaceOrientation)){
        screenBounds.size = CGSizeMake(width, height);
    }else if(UIInterfaceOrientationIsLandscape(interfaceOrientation)){
        screenBounds.size = CGSizeMake(height, width);
    }
    return screenBounds ;
}
Monjer
fonte
Quase igual à minha resposta, a única coisa que falta é se estiver no retrato de cabeça para baixo. Isso só importa se você apoiar essa orientação.
Robert Wagstaff
2
@Monjer você não deve nomear métodos que não executam uma solicitação GET, prefixados com a palavra get. currentScreenBoundsDependOnOrientation é um nome melhor para o método
bogen
1
@ Hakonbogen.yes pode estar certo, porque a declaração “peroperty” gera automaticamente o método setter / getter e isso pode levar a um conflito de nomenclatura e ir contra as convenções de nomenclatura objc. Obrigado pelo seu conselho.
segunda-
É bom que a Apple finalmente reconheceu tacitamente que seu código de rotação era uma bagunça completa, e apenas começou a nos dizer quais são as dimensões na orientação atual. Pena que demorou até a versão 8 para chegar lá.
MusiGenesis
30

Aqui está uma macro útil:

#define SCREEN_WIDTH (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation) ? [[UIScreen mainScreen] bounds].size.width : [[UIScreen mainScreen] bounds].size.height)
#define SCREEN_HEIGHT (UIInterfaceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation) ? [[UIScreen mainScreen] bounds].size.height : [[UIScreen mainScreen] bounds].size.width)
Robert Wagstaff
fonte
14

No iOS 8+, você deve usar o viewWillTransitionToSize:withTransitionCoordinatormétodo:

-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    // You can store size in an instance variable for later
    currentSize = size;

    // This is basically an animation block
    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {

        // Get the new orientation if you want
        UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];

        // Adjust your views
        [self.myView setFrame:CGRectMake(0, 0, size.width, size.height)];

    } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
        // Anything else you need to do at the end
    }];
}

Isso substitui o método de animação obsoleto que não fornecia informações sobre o tamanho:

-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
Mark Hennings
fonte
Esta deve ser a resposta aceita. Moderno e atualizado.
SarpErdag
Use esta resposta para iOS 8 e superior.
iPhoneDeveloper
10

A partir do iOS 8, os limites da tela agora são retornados corretos para a orientação atual. Isso significa que um iPad na orientação paisagem [UIScreen mainScreen] .bounds retornaria 768 no iOS <= 7 e 1024 no iOS 8.

O seguinte retorna a altura e largura corretas em todas as versões lançadas.

-(CGRect)currentScreenBoundsDependOnOrientation
{
    NSString *reqSysVer = @"8.0";
    NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
    if ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending)
        return [UIScreen mainScreen].bounds;

    CGRect screenBounds = [UIScreen mainScreen].bounds ;
    CGFloat width = CGRectGetWidth(screenBounds)  ;
    CGFloat height = CGRectGetHeight(screenBounds) ;
    UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation;

    if(UIInterfaceOrientationIsPortrait(interfaceOrientation)){
        screenBounds.size = CGSizeMake(width, height);
        NSLog(@"Portrait Height: %f", screenBounds.size.height);
    }else if(UIInterfaceOrientationIsLandscape(interfaceOrientation)){
        screenBounds.size = CGSizeMake(height, width);
        NSLog(@"Landscape Height: %f", screenBounds.size.height);
    }

    return screenBounds ;
}
A.Badger
fonte
8

se você quiser o tamanho dependente da orientação e tiver uma visualização, pode apenas usar:

view.bounds.size
Tod
fonte
ÓTIMO! Solução KISS = Keep It Simple and Stupid
bsorrentino
3
KISS não significa "Keep it Simple and Stupid" - LOL! Significa "Mantenha a simplicidade, estúpido!" :-)
Erik van der Neut
Além disso, essa resposta obviamente só funciona se sua exibição for exatamente de tela inteira. Mas, se for esse o caso, então provavelmente você não tem o problema original postado pelo OP.
Erik van der Neut
5

Eu escrevi categoria UIScreen, que funciona em todas as versões do iOS, para que você possa usá-lo como este:
[[UIScreen mainScreen] currentScreenSize].

@implementation UIScreen (ScreenSize)

- (CGSize)currentScreenSize {
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    CGSize screenSize = screenBounds.size;

    if ( NSFoundationVersionNumber <= NSFoundationVersionNumber_iOS_7_1 ) {  
        UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
        if ( UIInterfaceOrientationIsLandscape(interfaceOrientation) ) {
            screenSize = CGSizeMake(screenSize.height, screenSize.width);
        }
    }

    return screenSize;
}

@end
ArtFeel
fonte
1
Esta parece ser a resposta mais limpa para mim. Votado.
Erik van der Neut
5

Aqui está uma maneira rápida de obter tamanhos de tela dependentes da orientação:

var screenWidth: CGFloat {
    if UIInterfaceOrientationIsPortrait(screenOrientation) {
        return UIScreen.mainScreen().bounds.size.width
    } else {
        return UIScreen.mainScreen().bounds.size.height
    }
}
var screenHeight: CGFloat {
    if UIInterfaceOrientationIsPortrait(screenOrientation) {
        return UIScreen.mainScreen().bounds.size.height
    } else {
        return UIScreen.mainScreen().bounds.size.width
    }
}
var screenOrientation: UIInterfaceOrientation {
    return UIApplication.sharedApplication().statusBarOrientation
}

Estes são incluídos como uma função padrão em um projeto meu:

https://github.com/goktugyil/EZSwiftExtensions

Esqarrouth
fonte
0
float msWidth = [[UIScreen mainScreen] bounds].size.width*(IS_RETINA?2.0f:1.0f);
float msHeight = [[UIScreen mainScreen] bounds].size.height*(IS_RETINA?2.0f:1.0f);
if ( UIInterfaceOrientationIsPortrait(self.interfaceOrientation) ) {
    os->setWidth(MIN(msWidth, msHeight));
    os->setHeight(MAX(msWidth, msHeight));
} else {
    os->setWidth(MAX(msWidth, msHeight));
    os->setHeight(MIN(msWidth, msHeight));
}

NSLog(@"screen_w %f", os->getWidth());
NSLog(@"screen_h %f", os->getHeight());
user4082558
fonte
0

No entanto, no iOS 8.0.2:

+ (NSUInteger)currentWindowWidth
{
    NSInteger width = 0;
    UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
    CGSize size = [UIScreen mainScreen].bounds.size;
   // if (UIInterfaceOrientationIsLandscape(orientation)) {
   //     width = size.height;
   // } else {
        width = size.width;
  //  }

    return width;
}
Gank
fonte
0

use -> setNeedsDisplay () para a visão que você deseja redimensionar.

Santosh
fonte