Định cỡ lớp cho iPad Chế độ dọc và ngang


97

Về cơ bản, tôi muốn các chế độ xem phụ của mình được định vị khác nhau tùy thuộc vào hướng của iPad (Chân dung hoặc Phong cảnh) bằng cách sử dụng Các lớp định cỡ được giới thiệu trong xcode 6. Tôi đã tìm thấy nhiều hướng dẫn giải thích cách các lớp định cỡ khác nhau khả dụng cho Điện thoại ở chế độ dọc và ngang trên IB nhưng dường như không có chế độ nào bao gồm các chế độ ngang hoặc dọc riêng lẻ cho iPad trên IB. Có ai giúp được không?


Có vẻ như không phải là một giải pháp không có lập trình, đó sẽ là cần thiết cho màn hình mặc định
SwiftArchitect

Câu trả lời:


174

Có vẻ như ý định của Apple là coi cả hai hướng của iPad là giống nhau - nhưng như một số người trong chúng ta đang nhận thấy, có những lý do thiết kế rất chính đáng để muốn thay đổi bố cục giao diện người dùng cho iPad Portrait so với iPad Landscape.

Thật không may, hệ điều hành hiện tại dường như không cung cấp hỗ trợ cho sự khác biệt này ... có nghĩa là chúng ta đang quay lại thao tác với các ràng buộc bố cục tự động trong mã hoặc các cách giải quyết tương tự để đạt được những gì chúng ta lý tưởng có thể nhận được miễn phí bằng Giao diện người dùng thích ứng .

Không phải là một giải pháp thanh lịch.

Không có cách nào để tận dụng điều kỳ diệu mà Apple đã tích hợp sẵn trong IB và UIKit để sử dụng loại kích thước do chúng tôi lựa chọn cho một định hướng nhất định?

~

Khi suy nghĩ về vấn đề một cách tổng quát hơn, tôi nhận ra rằng 'các lớp kích thước' chỉ đơn giản là cách để giải quyết nhiều bố cục được lưu trữ trong IB, để chúng có thể được gọi lên khi cần thiết trong thời gian chạy.

Trên thực tế, một 'size class' thực sự chỉ là một cặp giá trị enum. Từ UIInterface.h:

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

Vì vậy, bất kể Apple đã quyết định đặt tên cho các biến thể khác nhau này là gì, về cơ bản, chúng chỉ là một cặp số nguyên được sử dụng làm mã định danh duy nhất để phân biệt bố cục này với bố cục khác, được lưu trữ trong IB.

Bây giờ, giả sử rằng chúng tôi tạo bố cục thay thế (sử dụng loại kích thước không được sử dụng) trong IB - giả sử, đối với iPad Portrait ... có cách nào để thiết bị sử dụng lựa chọn loại kích thước (bố cục giao diện người dùng) của chúng tôi khi cần thiết trong thời gian chạy không ?

Sau khi thử một số cách tiếp cận khác nhau (ít thanh lịch hơn) cho vấn đề, tôi nghi ngờ có thể có một cách để ghi đè lớp kích thước mặc định theo chương trình. Và có (trong 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);

Do đó, nếu bạn có thể đóng gói hệ thống phân cấp bộ điều khiển chế độ xem của mình dưới dạng bộ điều khiển chế độ xem 'con' và thêm nó vào bộ điều khiển chế độ xem cấp cao nhất ... thì bạn có thể ghi đè có điều kiện để trẻ nghĩ rằng đó là một lớp kích thước khác với mặc định từ hệ điều hành.

Đây là một triển khai mẫu thực hiện điều này, trong bộ điều khiển chế độ xem 'cha':

@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

Như một bản trình diễn nhanh để xem liệu nó có hoạt động hay không, tôi đã thêm các nhãn tùy chỉnh cụ thể vào phiên bản 'Thông thường / Thông thường' và 'Nhỏ gọn / Thông thường' của bố cục bộ điều khiển con trong IB:

nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

Và đây là những gì nó trông giống như đang chạy, khi iPad ở cả hai hướng: nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

Thì đấy! Cấu hình lớp kích thước tùy chỉnh trong thời gian chạy.

Hy vọng rằng Apple sẽ biến điều này trở nên không cần thiết trong phiên bản tiếp theo của hệ điều hành. Trong thời gian chờ đợi, đây có thể là một cách tiếp cận thanh lịch và có thể mở rộng hơn là việc lập trình gây rối với các ràng buộc bố cục tự động hoặc thực hiện các thao tác khác trong mã.

~

EDIT (6/4/15): Xin lưu ý rằng mã mẫu ở trên về cơ bản là một bằng chứng khái niệm để chứng minh kỹ thuật. Hãy tự do điều chỉnh nếu cần cho ứng dụng cụ thể của riêng bạn.

~

EDIT (24/7/15): Thật vui vì lời giải thích ở trên dường như giúp làm sáng tỏ vấn đề. Mặc dù tôi chưa thử nghiệm nó, nhưng mã của mohamede1945 [bên dưới] trông giống như một tối ưu hóa hữu ích cho các mục đích thực tế. Hãy thử nó ra và cho chúng tôi biết suy nghĩ của bạn. (Vì sự hoàn chỉnh, tôi sẽ để nguyên mã mẫu ở trên.)


1
Bài đăng tuyệt vời @RonDiamond!
amergin

3
Apple thực hiện một cách tiếp cận tương tự để xác định lại lớp kích thước chiều rộng trong Safari, vì vậy bạn có thể yên tâm rằng đây là một cách tiếp cận ít nhiều được hỗ trợ. Bạn không thực sự cần thêm ivars; UITraitCollectionđược tối ưu hóa đủ và overrideTraitCollectionForChildViewControllerhiếm khi được gọi là đủ để không phải là vấn đề khi thực hiện kiểm tra chiều rộng và tạo nó sau đó.
zwaldowski

1
@zwaldowski Cảm ơn. Mã mẫu ở đây chỉ là để trình diễn kỹ thuật và rõ ràng có thể được tối ưu hóa theo nhiều cách khác nhau như mong muốn. (Theo nguyên tắc chung, với một đối tượng được sử dụng lặp đi lặp lại [ví dụ: bất cứ khi nào thiết bị thay đổi hướng], tôi không nghĩ việc giữ chặt một đối tượng là một ý tưởng tồi; nhưng như bạn đã chỉ ra, sự khác biệt về hiệu suất ở đây có thể là tối thiểu.)
RonDiamond

1
@Rashmi: Như tôi đã ám chỉ trong phần giải thích, cuối cùng không quan trọng bạn chọn cái nào - bạn chỉ ánh xạ (các) bố cục với một cặp giá trị enum. Vì vậy, bạn thực sự có thể sử dụng bất kỳ "loại kích thước" nào phù hợp với bạn. Tôi chỉ muốn đảm bảo rằng nó không xung đột với một số lớp kích thước (hợp pháp) khác có thể được kế thừa ở đâu đó làm mặc định.
RonDiamond

1
Đối với bộ điều khiển chế độ xem trẻ em, tôi nghĩ không quan trọng. Bạn sẽ có thể khởi tạo nó theo chương trình hoặc từ ngòi, miễn là nó được thêm đúng cách làm bộ điều khiển con vào vùng chứa.
RonDiamond

41

Như một bản tóm tắt cho câu trả lời rất dài của RonDiamond. Tất cả những gì bạn cần làm là trong bộ điều khiển chế độ xem gốc của bạn.

Objective-c

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

Nhanh:

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

Sau đó, trong storyborad, sử dụng chiều rộng nhỏ gọn cho Chân dung và Chiều rộng thông thường cho Phong cảnh.


Tôi đang tự hỏi điều đó sẽ hoạt động như thế nào với các chế độ chia đôi màn hình mới ... một cách để tìm hiểu!
mm2001

UITraitCollection chỉ dành cho iOS 8.0+
mohamede1945,

Không hoạt động với tôi Tôi có một chế độ xem có hai chế độ xem vùng chứa, chúng cần được hiển thị ở một vị trí khác tùy thuộc vào hướng. Tôi đã tạo tất cả các lớp kích thước cần thiết, tất cả đều hoạt động cho iPhone. Tôi đã thử sử dụng mã của bạn để tách hướng iPad. Tuy nhiên, nó sẽ chỉ hành động kỳ lạ trên các lượt xem. Nó không thay đổi nó như nó phải làm. Bất kỳ manh mối những gì có thể đang xảy ra?
NoSixties

1
Điều này đã làm việc cho tôi khi tôi overrode - (UITraitCollection *)traitCollectionthay vì overrideTraitCollectionForChildViewController. Ngoài ra, các ràng buộc phải phù hợp với các đường dẫn, vì vậy wC (hAny).
H. de Jonge

1
@Apollo Tôi sẽ trả lời nhưng nó sẽ không trả lời câu hỏi thực tế. Hãy xem tại đây để biết mẫu của Apple về cách sử dụng setOverrideTraitCollection github.com/ios8/AdaptivePhotosAnAdaptiveApplication/blob/master/…
malhal

5

IPad có đặc điểm kích thước 'bình thường' cho cả chiều ngang và chiều dọc, không có sự phân biệt giữa dọc và ngang.

Các đặc điểm kích thước này có thể được ghi đè trong UIViewControllermã lớp con tùy chỉnh của bạn , thông qua phương thức traitCollection, ví dụ:

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

Điều này mang lại cho iPad các đặc điểm kích thước tương tự như iPhone 7 Plus. Lưu ý rằng các mẫu iPhone khác thường có đặc điểm 'chiều rộng nhỏ gọn' (thay vì chiều rộng thông thường) bất kể hướng nào.

Bắt chước iPhone 7 Plus theo cách này cho phép sử dụng kiểu máy đó làm giá đỡ cho iPad trong Trình tạo giao diện của Xcode mà không biết về các tùy chỉnh trong mã.

Hãy lưu ý rằng Chế độ xem phân tách trên iPad có thể sử dụng các đặc điểm kích thước khác với hoạt động toàn màn hình bình thường.

Câu trả lời này dựa trên cách tiếp cận được thực hiện trong bài đăng trên blog này , với một số cải tiến.

Cập nhật 2019-01-02: Đã cập nhật để sửa thanh trạng thái ẩn không liên tục trong màn hình iPad và có thể bị chà đạp các đặc điểm (mới hơn) trong UITraitCollection. Cũng lưu ý rằng tài liệu của Apple thực sự khuyến cáo không nên ghi đè traitCollection, vì vậy trong tương lai có thể có vấn đề với kỹ thuật này.


Apple chỉ định rằng thuộc traitCollectiontính ở chế độ chỉ đọc: developer.apple.com/documentation/uikit/uitraitenosystem/…
Mingbit

4

Câu trả lời dài và hữu ích của RonDiamond là một khởi đầu tốt để hiểu các nguyên tắc, tuy nhiên mã phù hợp với tôi (iOS 8+) dựa trên phương pháp ghi đè (UITraitCollection *)traitCollection

Vì vậy, hãy thêm các ràng buộc trong InterfaceBuilder với các biến thể cho Chiều rộng - Nhỏ gọn, ví dụ đối với thuộc tính Cài đặt của ràng buộc. Vì vậy, Chiều rộng - Bất kỳ sẽ có giá trị đối với chiều ngang, Chiều rộng - Nhỏ gọn đối với Chân dung.

Để chuyển đổi các ràng buộc trong mã dựa trên kích thước bộ điều khiển chế độ xem hiện tại, chỉ cần thêm phần sau vào lớp UIViewController của bạn:

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

Chế độ ngang của bạn sẽ khác bao nhiêu so với chế độ dọc của bạn? Nếu nó rất khác, bạn nên tạo một bộ điều khiển chế độ xem khác và tải nó khi thiết bị ở chế độ ngang

Ví dụ

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

Có đây là một lựa chọn. Nhưng tôi không nghĩ nó là tối ưu nhất. Quan điểm của tôi là nếu có tùy chọn sử dụng các đặc điểm phân loại kích thước khác nhau cho các chế độ dọc và ngang cho iPhone trong ios8 thì tại sao iPad lại không giống nhau?
neelIVP

Trước Xcode 6, chúng ta có thể sử dụng các bảng phân cảnh khác nhau cho các hướng khác nhau. Điều đó không hiệu quả lắm nếu hầu hết các bộ điều khiển chế độ xem đều giống nhau. Nhưng nó rất thuận tiện cho các bố cục khác nhau. Trong Xcode 6, không có cách nào để làm như vậy. Có thể tạo bộ điều khiển chế độ xem khác nhau cho các hướng khác nhau là giải pháp duy nhất.
Bagusflyer

2
Việc tải các viewController khác nhau mỗi lần dường như rất kém hiệu quả, đặc biệt nếu có điều gì đó xảy ra trên màn hình. Tốt hơn nhiều là sử dụng cùng một chế độ xem và thao tác ràng buộc tự động thanh toán hoặc vị trí của các phần tử trong mã. Hoặc sử dụng bản hack được đề cập ở trên, cho đến khi apple giải quyết vấn đề này.
Pahnev

0

Phiên bản Swift 5. Nó hoạt động tốt.

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

Mã Swift 3.0 cho giải pháp @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
}}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.