I have a simple layout with a constraint connecting two views and I store that constraint in a strong property.
My expectation is that after removing one of those views, the constraint will still exist, but it will turn inactive. But it is not the case and my app crashes with
EXC_BAD_ACCESS
exception.Luckily I am able to reproduce the issue. I attach the source code of the view controller. You can just paste it into a new project created with the "Single View App" template.
To reproduce the issue print the constraint description before and after removing one of the views. It happened to me once, that the constraint was not removed and the code worked (but it happened only once). With the debugger, I was able to check that the view I removed was not deallocated as well, while It already did not have a layer, so some parts of it were already deconstructed. After it happened I commented out the implementation of
printConstraintDescriptionButtonTouchedUpInside
and just set the breakpoint in the removeViewButtonTouchedUpInside
. When the debugger paused the app after pressing the button for the second time the constraint did not exist anymore.Is it disallowed to hold a strong reference to the
NSLayoutConstraint
instances? I have not found that information in the docs.Compiled with Xcode Version 10.1 (10B61), running iOS Simulator iPhone SE 12.1. The same logic but not in the isolated snippet fails on the physical devices running iOS 12.1.3 (16D5032a) and 11.2.2 (15C202). Compiling with Xcode Version 9.4.1 does not change anything.
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic) UIView* topView;
@property (strong, nonatomic) UIView* bottomView;
@property (strong, nonatomic) NSLayoutConstraint* constraint;
@property (strong, nonatomic) UIButton* removeViewButton;
@property (strong, nonatomic) UIButton* printConstraintDescriptionButton;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.topView = [UIView new];
self.topView.backgroundColor = [UIColor grayColor];
self.topView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.topView];
self.bottomView = [UIView new];
self.bottomView.backgroundColor = [UIColor grayColor];
self.bottomView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.bottomView];
self.removeViewButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.removeViewButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.removeViewButton setTitle:@"Remove View"
forState:UIControlStateNormal];
[self.removeViewButton addTarget:self
action:@selector(removeViewButtonTouchedUpInside)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.removeViewButton];
self.printConstraintDescriptionButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.printConstraintDescriptionButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.printConstraintDescriptionButton setTitle:@"Print Constraint Description"
forState:UIControlStateNormal];
[self.printConstraintDescriptionButton addTarget:self
action:@selector(printConstraintDescriptionButtonTouchedUpInside)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.printConstraintDescriptionButton];
NSDictionary* views = @{
@"topView": self.topView,
@"bottomView": self.bottomView,
@"removeViewButton": self.removeViewButton,
@"printConstraintDescriptionButton": self.printConstraintDescriptionButton
};
NSArray<NSString*>* constraints = @[
@"H:|-[topView]-|",
@"H:|-[bottomView]-|",
@"H:|-[printConstraintDescriptionButton]-|",
@"H:|-[removeViewButton]-|",
@"V:|-[topView(==44)]",
@"V:[bottomView(==44)]",
@"V:[printConstraintDescriptionButton]-[removeViewButton]-|"
];
[constraints enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:obj
options:0
metrics:nil
views:views]];
}];
self.constraint = [NSLayoutConstraint constraintWithItem:self.topView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.bottomView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:-8.0];
self.constraint.active = YES;
}
- (void)printConstraintDescriptionButtonTouchedUpInside {
NSLog(@"%@", self.constraint);
}
- (void)removeViewButtonTouchedUpInside {
[self.bottomView removeFromSuperview];
self.bottomView = nil;
}
@end