4

I have a view that needs different constraints between the iPad & iPhone version.

The login screen is presented as full screen on iPhone & in a page sheet on the iPad. Due to size the UITextFields are 40 pixels away from the right and left sides on the iPhone, but I want them to be 80 pixels on the iPad. I set this as a specific constraint for the Final Values Size Class (w Regular, h Regular).

However because this view appears on a page sheet it doesn't use the Final Values Size Class. Ideally I would like to "force" the view to use a size class, rather than having an array of iPhone/iPad constraints and lots of constraint Outlets.

William Robinson
  • 1,038
  • 17
  • 32
  • 1
    Why don't you create a helper function for your constraints? Then, what you will have to do is detect whatever device it is and pass the correct size you want to this function..? – valbu17 Feb 23 '15 at 14:40

2 Answers2

8

The way this is meant to be accomplished is with trait collections on UIViewController, easily by calling -setOverrideTraitCollection:forChildViewController:.

However, as the API title indicates, a UIViewController can only have its traitCollection overriden by its parent. Presented View Controllers aren't children of their parent (the header file specifically notes presented view controllers aren't included in Child View Controllers) so you will have to add a "ToPresentViewController".

In this example, in IB there is ToPresentViewController scene that is only a container ViewController attached to its sides with constraints of 0, and the view controller that will be presented embedded.

@implementation ToPresentViewController


-(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
    [super traitCollectionDidChange:previousTraitCollection];

    if(thisDevice.userInterfaceIdiom != UIUserInterfaceIdiomPad)
    {
        return;
    }

    for (UIViewController *controller in self.childViewControllers) { //Should be only one
        UITraitCollection *regularWidthTraits = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
        [self setOverrideTraitCollection:regularWidthTraits forChildViewController:controller];
    }

}

This will lock the size class to regular on iPad.

EDIT: The WWDC sample code here suggests indicates only the overridden traits need to be provided, which makes sense because in the original answer with a old+new merged collection it wasn't clear how it knew which to use.

Mike Sand
  • 2,750
  • 14
  • 23
  • I ran into an issue where trait-collection specific constraints were not getting installed in a VC of mine. Turned out this was being caused by the code overriding the entire trait collection rather than just the trait we wanted to modify. I only figured this out thanks to your edit where you indicated the WWDC sample code suggests only the overridden traits need be provided. THANK YOU. This isn't documented anywhere as far as I can tell. When you want to override traits in a trait collection, override only those traits, not the entire collection! – BreadicalMD Sep 27 '17 at 15:58
1

The way this is meant to be accomplished is with trait collections on UIViewController, easily by calling -setOverrideTraitCollection:forChildViewController:.

This means you will need to have your view here actually be an explicit child view controller, which has been the recommended practice for a while (and actually can be beneficial in other ways), and I think it might be automatic, but if not there are ways for the parent/child relationship to blend nicely with presenting/presented view controller relationship.

You will also need to make sure to create the trait collection correctly, which is described in the introduction in the UITraitCollection Class Reference

EDIT: This answer by Nick Lockwood, who wrote Core Animation Advanced Techniques, says "That's not how it's supposed to be used" BUT that answer is from 2012/iOS 5, and since then View Controller presentation has been streamlined and size classes (and the methods specifically to override them) have been introduced.

I think you could make the view controller a child and -setOverrideTraitCollection:forChildViewController: on it as or before you present it. In a worst case you could create a generic UIViewController, embed yours inside it as a child and present that. Let us know if any of these techniques work for you.

Community
  • 1
  • 1
Mike Sand
  • 2,750
  • 14
  • 23