Класс размера для iPad-портрета и ландшафтных режимов

В основном я хочу, чтобы мои подзаголовки располагались по-разному в зависимости от ориентации iPad (портрет или пейзаж), используя классы калибровки, представленные в xcode 6. Я нашел множество руководств, объясняющих, как доступны различные классы прошивки для Iphones в портретном и ландшафтном на IB, но, похоже, нет никого, что бы охватывало индивидуальные пейзажные или портретные режимы для iPad на IB. Может ли кто-нибудь помочь?

Ответ 1

Похоже, что Apple намерена рассматривать ориентацию iPad как одно и то же, но, как многие из нас находят, есть очень законные причины дизайна, которые хотят изменить макет интерфейса для iPad Portrait и iPad.

К сожалению, текущая ОС, похоже, не поддерживает эту разницу... означает, что мы вернулись к манипулированию ограничениями автоматической компоновки кода или аналогичных обходных решений, чтобы добиться того, что мы в идеале должны получить бесплатно используя Adaptive UI.

Не изящное решение.

Разве не существует способа использовать магию, которую Apple уже встроила в IB и UIKit, чтобы использовать класс размера по нашему выбору для заданной ориентации?

~

В более общем понимании проблемы я понял, что "классы размера" - это просто способы решения нескольких макетов, которые хранятся в IB, чтобы они могли быть вызваны по мере необходимости во время выполнения.

На самом деле "класс размера" - это всего лишь пара значений перечисления. Из UIInterface.h:

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

Таким образом, независимо от того, что Apple решила назвать эти разные варианты, в принципе, это всего лишь пара целых чисел, используемых как уникальный идентификатор сортов, чтобы отличить один макет от другого, сохраненный в IB.

Теперь, предположим, что мы создаем альтернативный макет (используя неиспользуемый класс размера) в IB - скажем, для iPad Portrait... есть способ использовать устройство для нашего выбора класса размера (макет пользовательского интерфейса), как необходимо во время выполнения?

Попробовав несколько разных (менее элегантных) подходов к проблеме, я подозревал, что может быть способ переопределить класс размера по умолчанию программным путем. И есть (в 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);

Таким образом, если вы можете упаковать иерархию контроллера вида в виде "дочернего" контроллера представления и добавить его в контроллер родительского представления верхнего уровня... тогда вы можете условно переопределить ребенка, думая, что это другой класс размера чем по умолчанию из ОС.

Здесь пример реализации, который делает это, в "родительском" контроллере представления:

@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

В качестве быстрой демонстрации, чтобы увидеть, сработала ли она, я добавил специальные метки специально для "Регулярных/Регулярных" и "Компактных/Обычных" версий макета дочернего контроллера в IB:

enter image description hereenter image description here

И вот что это похоже на работу, когда iPad находится в обеих ориентациях: enter image description hereenter image description here

Voila! Конфигурации пользовательского размера класса во время выполнения.

Надеюсь, Apple сделает это ненужным в следующей версии ОС. В то же время это может быть более элегантный и масштабируемый подход, чем программный беспорядок с ограничениями автоматического макета или другие манипуляции с кодом.

~

РЕДАКТИРОВАТЬ (6/4/15): Пожалуйста, имейте в виду, что приведенный выше пример кода является, по сути, доказательством концепции, демонстрирующей технику. Не стесняйтесь адаптироваться по мере необходимости для своего конкретного приложения.

~

РЕДАКТИРОВАТЬ (7/24/15): Отрадно, что приведенное выше объяснение, похоже, помогает демистифицировать проблему. Хотя я не тестировал его, код mohamede1945 [ниже] выглядит как полезная оптимизация для практических целей. Не стесняйтесь проверить это и сообщите нам, что вы думаете. (В интересах полноты, я оставлю образец кода выше как есть.)

Ответ 2

Как итог очень длинного ответа РонДиамонда. Все, что вам нужно сделать, находится в вашем контроллере корневого представления.

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

Затем в storyborad используйте компактную ширину для Portrait и Regular width для Landscape.

Ответ 3

iPad имеет "регулярную" черту размера как для горизонтального, так и для вертикального размеров, не делая различия между портретом и ландшафтом.

Эти значения размера могут быть переопределены в вашем пользовательском коде подкласса UIViewController с помощью метода traitCollection, например:

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

Это дает iPad такие же характеристики, как iPhone 7 Plus. Обратите внимание, что другие модели iPhone обычно имеют свойство "компактной ширины" (а не регулярную ширину) независимо от ориентации.

Имитация iPhone 7 Plus таким образом позволяет использовать эту модель в качестве платформы для iPad в Xcode Interface Builder, которая не знает о настройках в коде.

Имейте в виду, что Split View на iPad может использовать разные значения размера от нормальной полноэкранной работы.

Этот ответ основан на подходе, сделанном в этом сообщении в блоге с некоторыми улучшениями.

Ответ 4

Длительный и полезный ответ RonDiamond - хорошее начало для понимания принципов, однако код, который работал у меня (iOS 8+), основан на переопределяющем методе (UITraitCollection *)traitCollection

Итак, добавьте ограничения в InterfaceBuilder с вариантами для Width-Compact, например, для свойства ограничения Installed. So Width - Любой будет действителен для ландшафта, Width - Compact для Portrait.

Чтобы переключить ограничения в коде на основе текущего размера контроллера, просто добавьте следующее в свой класс 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]];
    }
}

Ответ 5

Насколько отличается ваш ландшафтный режим, чем ваш портретный режим? Если это совсем другое, может быть хорошей идеей создать другой контроллер представления и загрузить его, когда устройство находится в ландшафтном

Например

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

Ответ 6

Swift 3.0 для решения @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
}}