简体   繁体   中英

Can't change Constraint IBOutlet that is defined for different size classes in IB

I made a special test app for this case. (I'm sorry it is already removed)

I added a view on my controller's view in Storyboard , set up AutoLayout constraints in Interface Builder and made one of them (vertical space) is defferent for different size classes. Screenshot from IB

So the value is 100 for Any height, Any width and 0 for Regular height, Regular width . It works well, on iPhone vertical distance from top is 100, when on iPad it is 0.

Also I made IBOutlet for this constraint and want to change it in runtime to 10

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topVerticalConstraint;

it seemed I couldn't change it because it gives no effect

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.topVerticalConstraint.constant = 10; // it doesn't work
}

Although it works when I remove value for Regular height, Regular width in Interface Builder .

Am I miss something about the size classes?

The problem is that constraints are not fully defined yet until Layout events happen between -viewWillLayoutSubviews and -viewDidLayoutSubviews where all the parameters from IB comes into play. My rule of thumb is:

  • if you use frames to position your views manually you can do it as early as -viewDidLoad,
  • if you use autolayout constraints for positioning, make adjustments as early as -viewDidLayoutSubviews;

The second statements only considers code adjustments to constraints that have been made in IB. Adjustments that you are making in -viewDidLoad will be overridden by parameters set in IB during layout. If you add constraints with code you can set them in -viewDidLoad, since there will be nothing to override them.

I've changed your code a bit and it works:

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topVerticalConstraint;
@property (weak, nonatomic) IBOutlet UIView *square;

@property (assign, nonatomic) BOOL firstLayout;

@end

@implementation ViewController

- (void)viewDidLoad

{
    [super viewDidLoad];

    self.firstLayout = YES;
}

- (void)viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

    if (self.firstLayout) {

        self.topVerticalConstraint.constant = 10;
        self.firstLayout = NO;

    }
}

@end

Notice that -viewDidLayoutSubviews is called many times during the lifetime of a ViewController, so you have to make sure that your adjustments happen only once on initial load.

The problem:

If you set up different value for different size classes in IB for the constraint like this:

在此输入图像描述

then you can't change constant value in code like this:

self.adHeightConstraint.constant = 0;  // value set to 0
[self.view layoutIfNeeded];  // value get back to IB value (44 or 36)

In this situation you may see that your constant value persists only until views recalculates. So, after [self.view layoutIfNeeded] the value of constant reset back to whatever was set in IB.

The solution:

  1. Add the second constraint of the same attribute (in my case it was the height) with desired value. You may set this value in IB or change it in the code.
  2. Set low priority for this new constraint. Since it's low priority, it won't be any conflict.
  3. Now when you need to apply the new constant, simple disable the first constraint :

     self.adHeightConstraint.active = NO; [self.view layoutIfNeeded]; 

I have experienced the same issue, but it doesn't seem to have anything to do with viewDidLoad vs viewDidLayoutSubviews. Interface Builder can manage alternate constraint constants for different size classes, but when you try to update NSLayoutConstraint.constant in code, that constant isn't associated with any particular size class (including the active one).

From Apple docs, Changing Constraint Constants for a Size Class (XCode 7, Interface Builder) 多个大小类的XCode 7 Interface Builder约束

My solution has been to remove the alternate constants from IB, and manage the size-based constraint constant switch in code, only for those specific constraints that are updated/modified in code. Any constraints that are only managed via storyboard/IB can use the alternate-size constants as normal.

// XCode 7.0.1, Swift 2.0
static var isCompactHeight : Bool = false;
static var heightOffset : CGFloat {
    return (isCompactHeight ? compactHeightOffset : regularHeightOffset);
}

/* applyTheme can be called as early as viewDidLoad */
func applyTheme() {
    // This part could go wherever you handle orientation changes
    let appDelegate = UIApplication.sharedApplication().delegate;
    let window = appDelegate?.window;
    let verticalSizeClass = window??.traitCollection.verticalSizeClass ?? UIUserInterfaceSizeClass.Unspecified;

    isCompactHeight = (verticalSizeClass == UIUserInterfaceSizeClass.Compact);

    // Use heightOffset
    changingConstraint.constant = heightOffset;
}

I'm hoping that some later version of Swift/XCode introduces getters & setters that take size-based alternates into account, mirroring the functionality that's already available via IB.

我检查它正在工作的示例项目中的相同场景可能是您忘记连接NSLayoutConstraint topVerticalConstraint与storyboard。

Change constraint's constant in viewDidLayoutSubviews

- (void) viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    if (IS_IPHONE4) {
        self.topConstraint.constant = 10;
        self.bottomButtonTop.constant = 10;
        self.pageControlTopConstraint.constant = 5;
    }
}

Easiest solution:

if (self.view.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular && self.view.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
    // for iPhone
    cnicTopConstraint.constant = -60;    
} else {
    // for iPad
    cnicTopConstraint.constant = -120;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM