[objective-c] iPad 세로 및 가로 모드에 대한 크기 조정 클래스

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



답변

두 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 [아래]의 코드는 실용적인 목적을위한 유용한 최적화처럼 보입니다. 자유롭게 테스트하고 의견을 알려주십시오. (완벽 함을 위해 위의 샘플 코드는 그대로 두겠습니다.)


답변

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에서 세로에는 컴팩트 너비를 사용하고 가로에는 일반 너비를 사용합니다.


답변

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않도록 권장 하므로 앞으로이 기술에 문제가있을 수 있습니다.


답변

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


답변

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

예를 들면

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


답변

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


답변

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