Reputation: 24820
I would like to achieve which I'll explain with an example described as follow.
In MS Word, in a canvas, we can add several objects like arrow, circle, square etc. Then we can easily group them. Once objects are properly grouped, now matter how we scale, it is scaled as a group & gives us a perfect scaling. I wish to achieve same using Autolayout in Storyboard / xib. According to me all objects scales based on their individual constraints.
Here is an example image.
Here is the structure of above image.
View with white color background
View with gray color background
View with white color bg and on top-center
View with white color bg and in center-center
View with white bg and bottom left
View with white bg and bottom right
If the top-most parent view is scaled down, every other elements in it recursively should get scaled down.
How to achieve such functionality using Autolayout?
Edit:
Here - I've prepared a quick gif for the same.
Upvotes: 0
Views: 272
Reputation: 2916
I found a solution for this problem but maybe is not the best. The idea is to change the Width and Height of the root view, calculate the percentage of Width and Height variation from the initial state and apply this to all children constraint.
Code
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *heightConstraint;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *widthConstraint;
@property (strong, nonatomic) IBOutlet UIView *rootView;
@end
@implementation ViewController{
CGPoint dragPoint;
CGFloat startDragWidth;
CGFloat startDragHeight;
NSMutableDictionary *startDragConstraints;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(detectPan:)];
self.rootView.gestureRecognizers = @[panRecognizer];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
startDragHeight = self.heightConstraint.constant;
startDragWidth = self.widthConstraint.constant;
startDragConstraints = [NSMutableDictionary dictionary];
//I used i and j for setting an identifier on the constraint
for (int i = 0; i < [self.rootView.subviews count]; i++)
{
UIView *view = self.rootView.subviews[i];
//Looping childs Width and Height constraints
for(int j = 0; j < [view.constraints count]; j++)
{
NSLayoutConstraint *constraint = view.constraints[j];
constraint.identifier = [NSString stringWithFormat:@"%d%d", i, j];
[startDragConstraints setObject:[NSNumber numberWithFloat:constraint.constant] forKey:constraint.identifier];
}
}
//Looping childs Vertical space and Horizontal space constraints. They live in the parent view
for (int i = 0; i < [self.rootView.constraints count]; i++)
{
NSLayoutConstraint *constraint = self.rootView.constraints[i];
constraint.identifier = [NSString stringWithFormat:@"%d", i];
[startDragConstraints setObject:[NSNumber numberWithFloat:constraint.constant] forKey:constraint.identifier];
}
}
- (void) detectPan:(UIPanGestureRecognizer *) uiPanGestureRecognizer
{
dragPoint = [uiPanGestureRecognizer translationInView:self.rootView];
if(fabsf(dragPoint.x) > fabsf(dragPoint.y))
{
dragPoint.y = dragPoint.x;
}
else
{
dragPoint.x = dragPoint.y;
}
self.heightConstraint.constant = startDragHeight + dragPoint.y;
self.widthConstraint.constant = startDragWidth + dragPoint.x;
CGFloat heightDelta = (self.heightConstraint.constant - startDragHeight) / startDragHeight;
CGFloat widthDelta = (self.widthConstraint.constant - startDragWidth) / startDragWidth;
for(UIView *view in self.rootView.subviews)
{
for(NSLayoutConstraint *constraint in view.constraints)
{
float previousConstant = [[startDragConstraints objectForKey:constraint.identifier] floatValue];
if(constraint.firstAttribute == NSLayoutAttributeWidth) //Width constraint
{
constraint.constant = previousConstant + previousConstant * widthDelta;
}
else if(constraint.firstAttribute == NSLayoutAttributeHeight) //Height constraint
{
constraint.constant = previousConstant + previousConstant * heightDelta;
}
}
}
for(NSLayoutConstraint *constraint in self.rootView.constraints)
{
float previousConstant = [[startDragConstraints objectForKey:constraint.identifier] floatValue];
if(constraint.firstAttribute == NSLayoutAttributeLeading) //Horizontal space constraint
{
constraint.constant = previousConstant + previousConstant * widthDelta;
}
else if(constraint.firstAttribute == NSLayoutAttributeTop) //Vertical space constraint
{
constraint.constant = previousConstant + previousConstant * heightDelta;
}
}
}
@end
Upvotes: 1
Reputation: 77661
You can do this by using the multiplier
part of the constraint system.
Each constraint is just an equation...
calculatedSize = multiplier * inputSize + constant
The calculatedSize
is the size of your view or the distance from the edge etc...
The inputSize
(when used) is the size of another view when (for example) using equal widths etc...
In order to get the behaviour you are after you need the calculatedSize to always be a multiple of the inputSize and have the constant set to 0.
So, in your example animation. If the grey square is set to have a width
equal to the group width * 0.5
then it will always scale properly.
That is fine for the sizes. The gaps can't work like this though.
In order to get the gaps to scale properly you can't just have them as gaps. Gaps are not "objects" in the language of AutoLayout. You can't make one gap equal to another. Nor can you make a gap related to the width of another view.
What you can do though is replace the gaps with "spacer" views. These are actual UIViews that you add to your storyboard but then you make them hidden so that you can't see them at runtime.
So, going back to the grey square in the example you might have something like...
|[spacerView][greySquare]
Now set the width of the spacerView to be superView.width * 0.17
and the width of the greySquare to be superView.width * 0.5
.
// using the equation above
greySquare.width = superView.width * 0.5 + 0
spacerView.width = superView.width * 0.17 + 0
You can set the spacerView to hidden either in Interface Builder (this will hide it at design time) or in a runtime attribute (this will hide it at runtime).
Now when you resize the superView the spacer will grow relative to the amount you have grown the superView.
This can get quite complex though as you still need to make sure you have all the spacer views constrained properly.
Upvotes: 2