web-dev-qa-db-ja.com

初期UIViewControllerのトレイトコレクションをオーバーライドする方法は? (ストーリーボード付き)

IOS8を対象としたアプリがあり、最初のビューコントローラーはUISplitViewControllerです。私はストーリーボードを使用しているので、すべてを親切にインスタンス化してくれます。

私のデザインのため、iPhoneのポートレートモードでマスタービューと詳細ビューの両方を表示するには、SplitViewControllerが必要です。そのため、このUISplitViewControllerのトレイトコレクションをオーバーライドする方法を探しています。

使えることがわかりました

 override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator!) { ... }

ただし、残念ながら、子コントローラーの特性コレクションをオーバーライドする方法は次のとおりです。

setOverrideTraitCollection(collection: UITraitCollection!, forChildViewController childViewController: UIViewController!)

uISplitViewControllerサブクラスで自分自身のためにそうすることはできません。

Appleのサンプルアプリ Adaptive Photos をチェックしました。そして、このアプリでは、作成者は特別なTraitOverrideViewControllerをルートとして使用し、viewControllerセッターでいくつかの魔法を使用してすべてを機能させます。

それは私には恐ろしいように見えます。特性を無効にする方法はありますか?または、ない場合、ストーリーボードで同じハックを使用するにはどうすればよいですか?言い換えると、ストーリーボードでUISplitViewControllerの特性を処理するためだけに、いくつかのviewControllerをルートとして挿入する方法は?

12
Ilya Belikin

わかりました。これを回避する別の方法があればいいのですが、今のところ、コードをAppleの例からSwiftに変換し、ストーリーボードで使用できるように調整しました。

それは機能しますが、それでもこの目標をアーカイブするのはひどい方法だと思います。

私のTraitOverride.Swift:

import UIKit

class TraitOverride: UIViewController {

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    var forcedTraitCollection: UITraitCollection? {
        didSet {
            updateForcedTraitCollection()
        }
    }

    override func viewDidLoad() {
        setForcedTraitForSize(view.bounds.size)
    }

    var viewController: UIViewController? {
        willSet {
            if let previousVC = viewController {
                if newValue !== previousVC {
                    previousVC.willMoveToParentViewController(nil)
                    setOverrideTraitCollection(nil, forChildViewController: previousVC)
                    previousVC.view.removeFromSuperview()
                    previousVC.removeFromParentViewController()
                }
            }
        }

        didSet {
            if let vc = viewController {
                addChildViewController(vc)
                view.addSubview(vc.view)
                vc.didMoveToParentViewController(self)
                updateForcedTraitCollection()
            }
        }
    }

    override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator!) {
        setForcedTraitForSize(size)
        super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
    }

    func setForcedTraitForSize (size: CGSize) {

        let device = traitCollection.userInterfaceIdiom
        var portrait: Bool {
            if device == .Phone {
                return size.width > 320
            } else {
                return size.width > 768
            }
        }

        switch (device, portrait) {
        case (.Phone, true):
            forcedTraitCollection = UITraitCollection(horizontalSizeClass: .Regular)
        case (.Pad, false):
            forcedTraitCollection = UITraitCollection(horizontalSizeClass: .Compact)
        default:
            forcedTraitCollection = nil
        }
    }

    func updateForcedTraitCollection() {
        if let vc = viewController {
            setOverrideTraitCollection(self.forcedTraitCollection, forChildViewController: vc)
        }
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        performSegueWithIdentifier("toSplitVC", sender: self)
    }

    override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
        if segue.identifier == "toSplitVC" {
            let destinationVC = segue.destinationViewController as UIViewController
            viewController = destinationVC
        }
    }

    override func shouldAutomaticallyForwardAppearanceMethods() -> Bool {
        return true
    }

    override func shouldAutomaticallyForwardRotationMethods() -> Bool {
        return true
    }
}

これを機能させるには、ストーリーボードに新しいUIViewControllerを追加し、それをイニシャルにする必要があります。次のように、showsegueを実際のコントローラーに追加します。 storyboard

セグエに「toSplitVC」という名前を付ける必要があります。 segue name

初期コントローラーをTraitOverrideに設定します。 assign controller

今ではあなたにもうまくいくはずです。より良い方法やこれに欠陥があれば教えてください。

7
Ilya Belikin

Swiftここでの翻訳...そしておそらくそれを解決したと思います。

以下は私が解決しようとしてかなりの時間を費やしたものです-私のSplitViewをiPhone6 +で動作させる-これはCocoaソリューションです。

私のアプリケーションはTabBarベースであり、SplitViewにはナビゲーションコントローラーがあります。結局、私の問題は、setOverrideTraitCollectionが正しいターゲットに送信されていなかったことでした。

@interface myUITabBarController ()

@property (nonatomic, retain) UITraitCollection *overrideTraitCollection;

@end

@implementation myUITabBarController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self performTraitCollectionOverrideForSize:self.view.bounds.size];
}

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    NSLog(@"myUITabBarController %@", NSStringFromSelector(_cmd));
    [self performTraitCollectionOverrideForSize:size];

    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}

- (void)performTraitCollectionOverrideForSize:(CGSize)size
{
    NSLog(@"myUITabBarController %@", NSStringFromSelector(_cmd));

    _overrideTraitCollection = nil;

    if (size.width > 320.0)
    {
        _overrideTraitCollection = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
    }

    [self setOverrideTraitCollection:_overrideTraitCollection forChildViewController:self];

    for (UIViewController * view in self.childViewControllers)
    {
        [self setOverrideTraitCollection:_overrideTraitCollection forChildViewController:view];
        NSLog(@"myUITabBarController %@ AFTER  viewTrait=%@", NSStringFromSelector(_cmd), [view traitCollection]);
    }
}

@end
5
David Wilson

質問されてから1年以上経ちますが、私の答えは、受け入れられた答えで成功しなかった私のような人に役立つと思います。

解決策が本当に簡単なのに、traitCollection:メソッドをオーバーライドするだけです。これが私のアプリの例です:

- (UITraitCollection *)traitCollection {
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
        return super.traitCollection;
    } else {
        switch (self.modalPresentationStyle) {
            case UIModalPresentationFormSheet:
            case UIModalPresentationPopover:
                return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];

            default:
                return super.traitCollection;
        }
    }
}

コントローラーがポップオーバーまたはフォームシートとして表示される場合、iPadでコンパクトサイズクラスを強制するという考え方です。

それが役に立てば幸い。

更新:

Appleはこれを行うことを推奨していません:

TraitCollectionプロパティを直接使用します。オーバーライドしないでください。カスタム実装を提供しないでください。

私はもうこの方法を使用していません!現在、親のviewControlerクラスにoverrideTraitCollectionForChildViewController:を実装しています。

4
Oleg_Korchickiy

はい、関数をオーバーライドするには、カスタムコンテナのView Controllerを使用する必要があります viewWillTransitionToSize 。ストーリーボードを使用して、コンテナのViewControllerを初期として設定します。また、プログラムを使用して実装する この優れたアーティカル を参照することもできます。それによると、あなたの判断の肖像画にはいくつかの制限がある可能性があります:

var portrait: Bool {
    if device == .Phone {
       return size.width > 320
    } else {
       return size.width > 768
    }
}

以外

    if **size.width > size.height**{
        self.setOverrideTraitCollection(UITraitCollection(horizontalSizeClass: UIUserInterfaceSizeClass.Regular), forChildViewController: viewController)
    }
    else{
        self.setOverrideTraitCollection(nil, forChildViewController: viewController)
    }

「」

2
Jenus Dong

追加のトップレベルVCは単純なアプリではうまく機能しますが、親VCがないため、モーダルで提示されたVCには反映されません。したがって、別の場所に再度挿入する必要があります。 。

私が見つけたより良いアプローチは、UINavigationControllerをサブクラス化してから、ストーリーボードや通常UINavigationControllerを使用する他の場所でサブクラスを使用することでした。ストーリーボードの追加のVCクラッターを節約し、コードの余分なクラッターも節約します。

この例では、すべてのiPhoneで横向きに通常の水平サイズクラスを使用します。

@implementation MyNavigationController

- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
    UIDevice *device = [UIDevice currentDevice];

    if (device.userInterfaceIdiom == UIUserInterfaceIdiomPhone && CGRectGetWidth(childViewController.view.bounds) > CGRectGetHeight(childViewController.view.bounds)) {
        return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
    }

    return nil;
}

@end
2
trapper

@ Ilycaへの小道具

Swift

import UIKit

class TraitOverride: UIViewController {

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
    }

    var forcedTraitCollection: UITraitCollection? {
        didSet {
            updateForcedTraitCollection()
        }
    }

    override func viewDidLoad() {
        setForcedTraitForSize(size: view.bounds.size)
    }

    var viewController: UIViewController? {
        willSet {
            if let previousVC = viewController {
                if newValue !== previousVC {
                    previousVC.willMove(toParentViewController: nil)
                    setOverrideTraitCollection(nil, forChildViewController: previousVC)
                    previousVC.view.removeFromSuperview()
                    previousVC.removeFromParentViewController()
                }
            }
        }

        didSet {
            if let vc = viewController {
                addChildViewController(vc)
                view.addSubview(vc.view)
                vc.didMove(toParentViewController: self)
                updateForcedTraitCollection()
            }
        }
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        setForcedTraitForSize(size: size)
        super.viewWillTransition(to: size, with: coordinator)
    }

    func setForcedTraitForSize (size: CGSize) {

        let device = traitCollection.userInterfaceIdiom
        var portrait: Bool {
            if device == .phone {
                return size.width > 320
            } else {
                return size.width > 768
            }
        }

        switch (device, portrait) {
        case (.phone, true):
            forcedTraitCollection = UITraitCollection(horizontalSizeClass: .regular)
        case (.pad, false):
            forcedTraitCollection = UITraitCollection(horizontalSizeClass: .compact)
        default:
            forcedTraitCollection = nil
        }
    }

    func updateForcedTraitCollection() {
        if let vc = viewController {
            setOverrideTraitCollection(self.forcedTraitCollection, forChildViewController: vc)
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        performSegue(withIdentifier: "toSplitVC", sender: self)
    }

    override var shouldAutomaticallyForwardAppearanceMethods: Bool {
        return true
    }

    override func shouldAutomaticallyForwardRotationMethods() -> Bool {
        return true
    }
}
1
Brandon A