iPad 세로 및 가로 모드에 대한 크기 조정 클래스


97

기본적으로 xcode 6에 도입 된 크기 조정 클래스를 사용하여 iPad (세로 또는 가로)의 방향에 따라 하위 뷰를 다르게 배치하고 싶습니다. IB의 세로 및 가로에서 아이폰에 대해 다른 크기 조정 클래스를 사용할 수있는 방법을 설명하는 많은 자습서를 찾았습니다. 그러나 IB에서 iPad의 개별 가로 또는 세로 모드를 다루는 것은없는 것 같습니다. 누구든지 도울 수 있습니까?


기본 화면에 필요한
비프로 그래 매틱

답변:


174

두 iPad 방향을 동일하게 취급하는 것이 Apple의 의도 인 것처럼 보이지만, 많은 사람들이 발견 한 것처럼 iPad Portrait와 iPad Landscape의 UI 레이아웃을 변경하려는 합법적 인 디자인 이유가 있습니다.

안타깝게도 현재 OS는 이러한 구분을 지원하지 않는 것 같습니다. 즉, 적응 형 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의 경우 ... 런타임에 필요에 따라 장치가 선택한 크기 클래스 (UI 레이아웃)를 사용하도록 할 수 있습니다. ?

문제에 대해 여러 가지 (덜 우아함) 접근 방식을 시도한 후 프로그래밍 방식으로 기본 크기 클래스를 재정의하는 방법이있을 수 있다고 생각했습니다. 그리고 (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);

따라서 뷰 컨트롤러 계층을 '하위'뷰 컨트롤러로 패키징하고이를 최상위 상위 뷰 컨트롤러에 추가 할 수 있다면 조건부로 자식을 재정 의하여 기본 클래스와 다른 크기 클래스라고 생각할 수 있습니다. OS에서.

다음은 '부모'뷰 컨트롤러에서이를 수행하는 샘플 구현입니다.

@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

작동 여부를 확인하는 간단한 데모로 IB에서 하위 컨트롤러 레이아웃의 '일반 / 일반'및 '컴팩트 / 일반'버전에 특별히 사용자 지정 레이블을 추가했습니다.

여기에 이미지 설명 입력 여기에 이미지 설명 입력

그리고 iPad가 두 방향에있을 때 실행되는 모습은 다음과 같습니다. 여기에 이미지 설명 입력 여기에 이미지 설명 입력

짜잔! 런타임시 사용자 정의 크기 클래스 구성.

바라건대 애플은 다음 버전의 OS에서 이것을 불필요하게 만들 것입니다. 한편, 이것은 자동 레이아웃 제약을 프로그래밍 방식으로 엉망으로 만들거나 코드에서 다른 조작을 수행하는 것보다 더 우아하고 확장 가능한 접근 방식 일 수 있습니다.

~

편집 (6/4/15) : 위의 샘플 코드는 본질적으로 기술을 보여주는 개념 증명이라는 점을 명심하십시오. 자신의 특정 응용 분야에 필요에 따라 자유롭게 조정하십시오.

~

편집 (7/24/15) : 위의 설명이 문제를 이해하는 데 도움이되는 것 같아서 기쁩니다. 테스트하지는 않았지만 mohamede1945 [아래]의 코드는 실용적인 목적을위한 유용한 최적화처럼 보입니다. 자유롭게 테스트하고 의견을 알려주십시오. (완벽 함을 위해 위의 샘플 코드는 그대로 두겠습니다.)


1
그레이트 포스트 @RonDiamond!
amergin

3
Apple은 Safari에서 너비 크기 클래스를 재정의하는 것과 유사한 접근 방식을 취하므로 이것이 다소 지원되는 접근 방식이라는 것을 확신 할 수 있습니다. 추가 ivar가 실제로 필요하지 않아야합니다. UITraitCollection충분히 최적화되고 overrideTraitCollectionForChildViewController드물게 호출되어 너비 검사를 수행하고 생성하는 것이 문제가되지 않아야합니다.
zwaldowski

1
@zwaldowski 감사합니다. 여기에있는 샘플 코드는 기술을 보여주기위한 것이며 원하는대로 다양한 방법으로 최적화 할 수 있습니다. (일반적으로 객체가 반복적으로 사용되는 경우 (예 : 장치의 방향이 바뀔 때마다) 객체를 붙잡는 것은 나쁜 생각이 아니라고 생각합니다. 그러나 지적했듯이 여기서 성능 차이는 최소 일 수 있습니다.)
RonDiamond 2015

1
@Rashmi : 설명에서 언급했듯이 궁극적으로 선택하는 것은 중요하지 않습니다. 레이아웃을 열거 형 값 쌍에 매핑하는 것입니다. 그래서 당신은 정말로 당신에게 의미가있는 "크기 클래스"를 사용할 수 있습니다. 기본적으로 어딘가에 상속 될 수있는 다른 (합법적 인) 크기 클래스와 충돌하지 않는지 확인합니다.
RonDiamond

1
자식 뷰 컨트롤러는 중요하지 않다고 생각합니다. 컨테이너에 자식 컨트롤러로 올바르게 추가되는 한 프로그래밍 방식으로 또는 펜촉에서 인스턴스화 할 수 있어야합니다.
RonDiamond

41

RonDiamond의 매우 긴 답변에 대한 요약. 루트 뷰 컨트롤러 만 있으면됩니다.

목표 -c

- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
    if (CGRectGetWidth(self.view.bounds) < CGRectGetHeight(self.view.bounds)) {
        return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
    } else {
        return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
    }
}

빠른:

override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection! {
        if view.bounds.width < view.bounds.height {
            return UITraitCollection(horizontalSizeClass: .Compact)
        } else {
            return UITraitCollection(horizontalSizeClass: .Regular)
        }
    }

그런 다음 storyborad에서 세로에는 컴팩트 너비를 사용하고 가로에는 일반 너비를 사용합니다.


새로운 분할 화면 모드로 어떻게 작동할지 궁금합니다 ... 알아내는 한 가지 방법입니다!
mm2001

UITraitCollection는 아이폰 OS 8.0 이상입니다
mohamede1945

나를 위해 작동하지 않습니다. 방향에 따라 다른 위치에 표시되어야하는 두 개의 컨테이너 뷰가있는 뷰가 있습니다. iPhone에서 작동하는 모든 필요한 크기 클래스를 만들었습니다. 코드를 사용하여 iPad 방향도 분리 해 보았습니다. 그러나 그것은 단지보기에 이상하게 행동 할 것입니다. 예상대로 변경되지 않습니다. 무슨 일이 일어나고 있는지 알 수 있습니까?
NoSixties

1
이것은 내가 - (UITraitCollection *)traitCollection대신 오버로딩했을 때 나를 위해 일했습니다 overrideTraitCollectionForChildViewController. 또한 제약 조건은 traitcollections와 일치해야하므로 wC (hAny).
H. de Jonge

1
@Apollo 나는 그럴 것이지만 실제 질문에 대답하지 않을 것입니다. setOverrideTraitCollection을 사용하는 방법에 대한 Apple의 샘플을 여기에서 확인하십시오. github.com/ios8/AdaptivePhotosAnAdaptiveApplication/blob/master/…
malhal

5

iPad는 가로 및 세로 크기 모두에 대해 '일반적인'크기 특성을 가지고있어 세로와 가로를 구분하지 않습니다.

이러한 크기 특성은 메서드를 통해 사용자 지정 UIViewController하위 클래스 코드 에서 재정의 할 수 있습니다 traitCollection. 예를 들면 다음과 같습니다.

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

이것은 iPad에 iPhone 7 Plus와 동일한 크기 특성을 제공합니다. 다른 iPhone 모델은 일반적으로 방향에 관계없이 일반 너비가 아닌 '콤팩트 너비'특성을 가지고 있습니다.

이러한 방식으로 iPhone 7 Plus를 모방하면 해당 모델이 코드의 사용자 정의를 인식하지 못하는 Xcode의 Interface Builder에서 iPad의 스탠드 인으로 사용될 수 있습니다.

iPad의 Split View는 일반적인 전체 화면 작동과 다른 크기 특성을 사용할 수 있습니다.

이 답변은 이 블로그 게시물 에서 취한 접근 방식을 기반으로하며 일부 개선 사항이 있습니다.

업데이트 2019-01-02 : iPad 환경에서 간헐적으로 숨겨진 상태 표시 줄과 .NET 에서 (최신) 특성의 잠재적 인 짓밟 기를 수정하도록 업데이트 되었습니다 UITraitCollection. 또한 Apple 문서는 실제로을 재정의하지 traitCollection않도록 권장 하므로 앞으로이 기술에 문제가있을 수 있습니다.


Apple은 traitCollection속성이 읽기 전용 임을 지정합니다 . developer.apple.com/documentation/uikit/uitraitenvironment/…
cleverbit

4

RonDiamond의 길고 유용한 답변은 원칙을 이해하는 좋은 시작이지만 저에게 효과가 있었던 코드 (iOS 8 이상)는 재정의 방법을 기반으로합니다. (UITraitCollection *)traitCollection

따라서 Width-Compact에 대한 변형을 사용하여 InterfaceBuilder에 제약 조건을 추가합니다 (예 : 제약 조건의 속성 설치됨). 따라서 Width-Any는 가로에 유효하고 Width-Compact는 세로에 유효합니다.

현재 뷰 컨트롤러 크기에 따라 코드에서 제약 조건을 전환하려면 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]];
    }
}

0

가로 모드는 세로 모드와 얼마나 다를까요? 매우 다른 경우 다른 뷰 컨트롤러를 만들고 장치가 가로 모드 일 때로드하는 것이 좋습니다.

예를 들면

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

예 이것은 하나의 옵션입니다. 그러나 나는 그것이 가장 최적이라고 생각하지 않습니다. 내 요점은 ios8에서 iPhone의 세로 및 가로 모드에 대해 다른 크기 조정 클래스 특성을 사용하는 옵션이 있다면 iPad에서도 동일하지 않은 이유는 무엇입니까?
neelIVP 2014 년

Xcode 6 이전에는 다른 방향에 대해 다른 스토리 보드를 사용할 수 있습니다. 대부분의 뷰 컨트롤러가 동일하다면 그다지 효율적이지 않습니다. 그러나 다른 레이아웃에 매우 편리합니다. Xcode 6에서는 그렇게 할 수있는 방법이 없습니다. 다른 방향에 대해 다른 뷰 컨트롤러를 만드는 것이 유일한 해결책 일 수 있습니다.
Bagusflyer 2014

2
매번 다른 viewController를로드하는 것은 매우 비효율적으로 보입니다. 특히 화면에 무언가가 발생하는 경우 더욱 그렇습니다. 동일한 뷰를 사용하고 코드에서 자동 레이아웃 제약 조건이나 요소의 위치를 ​​조작하는 것이 훨씬 좋습니다. 또는 애플이이 문제를 해결할 때까지 위에서 언급 한 해킹을 사용합니다.
Pahnev

0

Swift 5 버전. 잘 작동합니다.

override func overrideTraitCollection(forChild childViewController: UIViewController) -> UITraitCollection? {
    if UIScreen.main.bounds.width > UIScreen.main.bounds.height {
        let collections = [UITraitCollection(horizontalSizeClass: .regular),
                           UITraitCollection(verticalSizeClass: .compact)]
        return UITraitCollection(traitsFrom: collections)
    }
    return super.overrideTraitCollection(forChild: childViewController)
}

-3

@RonDiamond 솔루션 용 Swift 3.0 코드

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
}}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.