Domanda Come sovrascrivere la raccolta di tratti per UIViewController iniziale? (con Storyboard)


Ho un'app mirata su iOS8 e il controller di visualizzazione iniziale è UISplitViewController. Io uso lo storyboard, in modo da creare istanzialmente tutto per me.

A causa del mio design ho bisogno di SplitViewController per mostrare sia le viste principali che quelle di dettaglio in modalità verticale su iPhone. Quindi sto cercando un modo per sovrascrivere la raccolta di tratti per questo UISplitViewController.

Ho scoperto che posso usare

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

ma, sfortunatamente, esistono solo metodi per sovrascrivere le raccolte di tratti dei controller secondari:

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

e non posso farlo per me stesso nella mia sottoclasse UISplitViewController.

Ho controllato un'app di esempio Foto adattive da Apple. E in questa app l'autore usa TraitOverrideViewController come root e un po 'di magia nel suo viewController setter per far funzionare tutto.

Mi sembra orribile. C'è un modo per aggirare i tratti? O se non ci sono, come posso usare lo stesso hack con lo storyboard? In altre parole, come iniettare qualche viewController come root uno solo per gestire i tratti del mio UISplitViewController con storyboard?


10
2017-08-25 10:01


origine


risposte:


Ok, vorrei che ci fosse un altro modo per aggirare questo, ma per ora ho appena convertito il codice dall'esempio di Apple a Swift e l'ho regolato per usarlo con Storyboard.

Funziona, ma credo ancora che sia un modo terribile per archiviare questo obiettivo.

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

Per farlo funzionare è necessario aggiungere un nuovo UIViewController sullo storyboard e renderlo l'iniziale. Aggiungi mostra segui da esso al tuo controller reale in questo modo: storyboard

È necessario nominare il seguito "toSplitVC": segue name

e impostare il controller iniziale su TraitOverride: assign controller

Ora dovrebbe funzionare anche per te. Fammi sapere se trovi un modo migliore o qualche difetto in questo.


6
2017-08-29 09:50



Capisco che volevi una traduzione SWIFT qui ... e probabilmente l'hai risolto.

Di seguito è qualcosa che ho trascorso un tempo considerevole cercando di risolvere - ottenere il mio SplitView a lavorare su un iPhone 6+ - questa è una soluzione Cocoa.

La mia applicazione è basata su TabBar e SplitView ha controller di navigazione. Alla fine il mio problema era che setOverrideTraitCollection non veniva inviato alla destinazione corretta.

@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
2018-01-11 01:46



So che è passato più di un anno dall'ultima domanda, ma penso che la mia risposta aiuterà qualcuno come me che non ha raggiunto il successo con la risposta accettata.

Whell la soluzione è davvero semplice, puoi semplicemente scavalcare traitCollection: metodo. Ecco un esempio dalla mia app:

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

l'idea è di forzare la classe di dimensioni compatte su iPad se il controller viene presentato come popover o foglio di modulo.

Spero che sia d'aiuto.

AGGIORNARE: 

Apple non consiglia di fare questo:

Utilizza direttamente la proprietà traitCollection. Non ignorarlo. Non   fornire un'implementazione personalizzata.

Non sto più usando questo metodo! Ora sto implementando overrideTraitCollectionForChildViewController: nella classe genitore viewControler.


4
2018-01-20 19:46



Sì, deve utilizzare il contenitore personalizzato View Controller per sovrascrivere la funzione viewWillTransitionToSize. Si utilizza lo storyboard per impostare il contenitore View Controller come iniziale. Inoltre, puoi fare riferimento questo buon artiche che usano il programma per implementarlo. In base a ciò, il tuo portamento di giudizio potrebbe avere alcune limitazioni:

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

diverso da

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

"


1
2017-12-02 10:21



Il VC di livello superiore extra funziona bene per una semplice app, ma non si propaga a VC presentati in modo modale in quanto non hanno un parentVC. Quindi è necessario inserirlo di nuovo in posti diversi.

Un approccio migliore che ho trovato era solo per sottoclasse UINavigationController e quindi basta usare la sottoclasse nello storyboard e altrove dove normalmente useresti UINavigationController. Salva la confusione aggiuntiva di VC negli storyboard e salva anche ulteriore confusione nel codice.

Questo esempio farà sì che tutti gli iPhone usino una normale classe di dimensione orizzontale per il paesaggio.

@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

1
2017-11-24 01:15



Puntelli su @Ilyca

Swift 3

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
2018-04-25 00:20