Авторотация сложных интерфейсов в программах для iPad

Большая часть программ для iPhone и iPod touch поддерживают только портретную ориентацию. Многие разработчики даже не задумывались о том, чтобы добавить ландшафтную (альбомную) ориентацию там, где она действительно нужна. Не исключение и компания Apple, которая в iPhone OS 2.x подавала не самые лучшие примеры, не реализовывая поддержку ландшафтной (альбомной) ориентации в важных системных программах (в iPhone OS 3.x компания исправила свою ошибку).

С приходом iPad ситуация изменилась. Компания Apple обязала всех разработчиков поддерживать все ориентации устройства в программах для iPad. Естественно, могут быть исключения, например, игры. Но программы, которые не имеют жёсткой привязки (функциональной) к ориентации, должны быть дружелюбны к пользователю в любом положении устройства.

С простыми интерфейсами сложностей никаких нет. Объектам класса UIView задаётся необходимое свойство autoresizingMask, которое описывает изменение фрейма. К сложным интерфейсами такой метод уже не подходит.

Я расскажу, как сделать плавную и приятную авторотацию интерфейса программы с помощью метода layoutSubviews. Получить детальную информацию об этом методе можно в iPad Programming Guide. Я лишь приведу реальный практически пример его использования (документация от Apple скупа в этом плане).

Это один из вариантов, но результат его работы очень качественный. Естественно, этот вариант подходит для iPad и iPhone / iPod touch.

Допустим, мы хотим иметь следующие портретный и ландшафтный (альбомный) виды:



Сразу выкладываю финальный проект RotateDemo.

И видео:


Создадим базовый проект для iPad (Window-based).

Я привык весь интерфейс делать программно. Если вы хотите, то легко можете заменить часть кода на щёлканье мышкой в Interface Builder.

Добавим в проект файлы класса ContainerView (производный от UIView). Оставим пока его пустым. Объект этого класса будет контейнером всех объектов UIView в нашем контроллере. Именно в этом класса мы переназначим метод layoutSubviews.

Создадим файлы класса DemoViewController (производный от UIViewController).

Наш интерфейсный файл DemoViewController.h:

#import <UIKit/UIKit.h>
#import "ContainerView.h"

@interface DemoViewController : UIViewController
{
  ContainerView *containerView;
}

@end

* This source code was highlighted with Source Code Highlighter.


В реализации класса нам важен только один основной метод loadView:

- (void)loadView
{
  CGRect screenRect = [[UIScreen mainScreen] applicationFrame];

  UIView *contentView = [[UIView alloc] initWithFrame:screenRect];
  contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  contentView.backgroundColor = [UIColor blueColor];
  self.view = contentView;
  [contentView release];

  containerView = [[ContainerView alloc] initWithFrame:CGRectMake(0, 0, screenRect.size.width, screenRect.size.height)];
  containerView.backgroundColor = [UIColor blueColor];  
  containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  [self.view addSubview:containerView];
  
  UIButton *button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  button1.frame = CGRectZero;
  [button1 setTitle:@"Object 1" forState:UIControlStateNormal];
  button1.tag = 1001;
  [containerView addSubview:button1];
  
  UIButton *button2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  button2.frame = CGRectZero;
  [button2 setTitle:@"Object 2" forState:UIControlStateNormal];
  button2.tag = 1002;  
  [containerView addSubview:button2];

  UIButton *button3 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  button3.frame = CGRectZero;
  [button3 setTitle:@"Object 3" forState:UIControlStateNormal];
  button3.tag = 1003;  
  [containerView addSubview:button3];

  UIButton *button4 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  button4.frame = CGRectZero;
  [button4 setTitle:@"Object 4" forState:UIControlStateNormal];
  button4.tag = 1004;  
  [containerView addSubview:button4];  
}

* This source code was highlighted with Source Code Highlighter.


Здесь всё просто. Мы создаём контейнер и добавляем в него все наши элементы. В примере это просто 4 кнопки UIButton. Фреймы кнопок не имеют никакого значения (они будут установлены в методе layoutSubviews).

Возвращаемся к нашему классу ContainerView и переопределяем метод layoutSubviews:

- (void)layoutSubviews
{
  [super layoutSubviews];
  
  if (self.frame.size.width == 768)
  {  
    UIView *view = [self viewWithTag:1001];
    view.frame = CGRectMake(184, 100, 400, 150);
    
    view = [self viewWithTag:1002];
    view.frame = CGRectMake(184, 300, 400, 150);
    
    view = [self viewWithTag:1003];
    view.frame = CGRectMake(184, 500, 400, 150);
    
    view = [self viewWithTag:1004];
    view.frame = CGRectMake(184, 700, 400, 150);
  }
  else
  {
    UIView *view = [self viewWithTag:1001];
    view.frame = CGRectMake(74, 100, 400, 150);
    
    view = [self viewWithTag:1002];
    view.frame = CGRectMake(550, 100, 400, 150);

    view = [self viewWithTag:1003];
    view.frame = CGRectMake(74, 400, 400, 150);
    
    view = [self viewWithTag:1004];
    view.frame = CGRectMake(550, 400, 400, 150);
  }
}


* This source code was highlighted with Source Code Highlighter.


В примере используется сравнение с абсолютной шириной. Как вариант, можно использовать универсальную проверку:

if (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]))

* This source code was highlighted with Source Code Highlighter.


Готово. Осталось только активизировать наш контроллер в классе делегата:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{    
  controller = [[DemoViewController alloc] initWithNibName:nil bundle:nil];
  window.backgroundColor = [UIColor blueColor];
  [window addSubview:controller.view];
  [window makeKeyAndVisible];
  
  return YES;
}


* This source code was highlighted with Source Code Highlighter.


Вот мы и получили плавную анимацию изменения сложного интерфейса при смене ориентации. Минимум кода и ничего сложного.
+28
31 мая 2010, 12:56
43
Kyrie1965 55,6

комментарии (17)

+4
Ernesto #
спасибо
очень доступно
0
Krypt #
Как в тему. Как раз пишу подобное, будет пример, куда глянуть.
+3
OgrePet #
if (self.frame.size.width == 768)

Сурово, а если ширина контейнера будет не на весь экран? Лучше все таки использовать ориентацию экрана, а не ширину для проверки :)
–3
Kyrie1965 #
Тут всё зависит от конкретной ситуации. В приведённом примере контейнер заполняет всю экранную область — никаких противопоказаний в использовании абсолютной величины при сравнении нет. В этом нет ничего плохого.
+4
akirsanov #
Магические константы это плохо. Особенно плохо(при наличии оного) вместо первичного источника данных — ориентации экрана, упираться на следствие — ширину или высоту.
–2
Kyrie1965 #
Я не спорю, что можно использовать макрос UIInterfaceOrientationIsPortrait.

Но мне не очень понятно, чем именно особенно плох мой пример?

Это не универсальный код, а специально для iPad. 768 — это не какая-то магическая константа, а вполне уникальное число для конкретного примера. Эта программа для iPad, который имеет одно заведомо известное разрешение.
+6
shadart #
Ну вот в том и проблема, что не универсальный. :) Сегодня вы пишите специально для айпад, а завтра (внезапно!) выходит айпад %random%G с другим разрешением и тысячи программеров делавших софт по такому вот примеру должны будут вносить правки в свой код.

p.s.
Спасибо за поправку. ;)
+2
Kyrie1965 #
Добавил в топик вариант с макросом.
0
MOVe #
Ландшафтную ориентацию я бы лучше назвал «альбомной» иначе с самого начала не очень понятно, о чём речь.
0
Kyrie1965 #
Добавил в скобках.
+1
SAKrisT #
я примерно так же делаю, но через центр и с доп вычислениями

чтоб не проверять по ширине в кокой мы ориентации, думаю лучше использовать UIDeviceOrientationIsLandscape(orientation)
+1
SAKrisT #
сколько раз убеждаюсь, что нужно больше читать доку по framworks! Обратил внимание на viewWithTag, такая мелочь, а полезно, как-то раньше не встречал, хоть и активно параметр tag использую! Спасибо!
+2
Veliant #
тогда уж лучше делать сравнение self.frame.size.height > self.frame.size.width будет универсально
–2
Funbit #
Приятно встретить человека, который не пользуется Interface Builder'ом :)
+3
destman #
И это зря. Саппортить проекты построенные не интерфейс билдером — гораздо сложнее.
Особенно если понадобилось поменять, допустим, цвет шрифта или еще какую мелочь, а создатель проекта не удосужился сделать дефайну (или еще чего подобного) для этого.
В этом случае интерфес билдер ускорит подобный фикс в разы :)
Да и в коде меньше хлама в итоге. Остаются только вещи которые действительно связаны с програмированием (локализиция например).
Кроме того работать с дизайнером гораздо проще получается в IB чем постоянно перекомпиливая проект…

З.Ы. Скорость загрузки данных из xib конешне ниже чем кодом, но в большинстве случаев это абсолютно не критично.
0
Funbit #
Ну, дефайны у меня все-таки есть. Даже сделал для себя небольшой фреймворк, для удобного расположения контролов программным способом. Код получается куда прозрачнее. С поддержкой нет никаких проблем, даже при переходе на айпад (хотя под него все равно нужно переделывать весь интерфейс для новых гайдлайнов). Да и контролы, которые я пишу, в IB не реализовать, много различного рода custom view, лейеров и прочего.
0
destman #
Крайности — это всегда крайности :)
ИМХО для стандартных случаев — лучше использовать стандартне подходы.
Для какихто чуть более сложных случаев — свои компоненты (IB тут действительно почти ничем не поможет, хотя опять же расстановку элементов можно сделать там и не перегружать этой инфой код).
В тяжелых ситуациях (например куча кнопок, анимаций и картинок на фоне сложных расчетов) — наверно стоит подумать про OpenGL (тут уж IB почти не при делах).

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.