I want to display UIView on the bottom of the screen in portrait mode, so when a phone is rotated and horizontal orientation would reposition/resize all subviews, that one UIView would remain where it was, with the same size and original position (i.e. on the right end of horizontal orientation if it was on the bottom of portrait mode).
Is there a good way to do it?
You can set the autoresizing mask like this:
myView.autorezisingmask = UIViewAutorezingMaskFlexibleTopMargin;
This will keep the view at the bottom.
I can think of several ways to do this. One way, that I show below relies solely on using constraints. For this to work the 3 buttons should not be in their own transparent view, but just be subviews of the view you want to rotate (that is self.view in my example). I don't think the original constraints to these 3 buttons matters, because I remove them on rotation, but the constraints I started with had the center button with a centerX constraint, a fixed distance to the bottom, standard horizontal distances to the left and right buttons, and all three buttons had their baselines aligned. In viewDidLoad I loop through all of self.view's constraints and identify all that have to do with these buttons, and put them into an array so I can remove them and add them back later.
#interface ViewController ()
#property (strong,nonatomic) NSMutableArray *portraitConstraints;
#property (strong,nonatomic) NSMutableArray *landscapeConstraints;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.portraitConstraints = [NSMutableArray array];
self.landscapeConstraints = [NSMutableArray array];
for (NSLayoutConstraint *con in self.view.constraints) {
if (con.firstItem == self.leftButton || con.secondItem == self.leftButton || con.firstItem == self.centerButton || con.secondItem == self.centerButton || con.firstItem == self.rightButton || con.secondItem == self.rightButton) {
[self.portraitConstraints addObject:con];
}
}
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeRight:{
[self.view removeConstraints:self.portraitConstraints];
[self.view removeConstraints:self.landscapeConstraints];
[self.landscapeConstraints removeAllObjects];
NSLayoutConstraint *centerYCon = [NSLayoutConstraint constraintWithItem:self.centerButton attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *rightCon = [NSLayoutConstraint constraintWithItem:self.centerButton attribute:NSLayoutAttributeRight relatedBy:0 toItem:self.view attribute:NSLayoutAttributeRight multiplier:1 constant:-8];
NSArray *stackCons= [NSLayoutConstraint constraintsWithVisualFormat:#"V:[left]-[center]-[right]" options:NSLayoutFormatAlignAllLeading metrics:nil views:#{#"left":self.leftButton, #"center":self.centerButton, #"right":self.rightButton}];
[self.landscapeConstraints addObject:centerYCon];
[self.landscapeConstraints addObject:rightCon];
[self.landscapeConstraints addObjectsFromArray:stackCons];
[self.view addConstraints:self.landscapeConstraints];
break;
}
case UIInterfaceOrientationLandscapeLeft:{
[self.view removeConstraints:self.portraitConstraints];
[self.view removeConstraints:self.landscapeConstraints];
[self.landscapeConstraints removeAllObjects];
NSLayoutConstraint *centerYCon2 = [NSLayoutConstraint constraintWithItem:self.centerButton attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
NSLayoutConstraint *leftCon = [NSLayoutConstraint constraintWithItem:self.centerButton attribute:NSLayoutAttributeLeft relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:8];
NSArray *stackCons2= [NSLayoutConstraint constraintsWithVisualFormat:#"V:[left]-[center]-[right]" options:NSLayoutFormatAlignAllLeading metrics:nil views:#{#"left":self.leftButton, #"center":self.centerButton, #"right":self.rightButton}];
[self.landscapeConstraints addObject:centerYCon2];
[self.landscapeConstraints addObject:leftCon];
[self.landscapeConstraints addObjectsFromArray:stackCons2];
[self.view addConstraints:self.landscapeConstraints];
break;
}
case UIInterfaceOrientationPortrait:{
[self.view removeConstraints:self.landscapeConstraints];
[self.view addConstraints:self.portraitConstraints];
break;
}
default:
break;
}
}
Related
I would like to add images depending on the st_pov. This code add one image. I need image below image, like list.
The code:
for (int i = 0; i < st_pov; i++)
{
picture_pov.image=[UIImage imageNamed:#"picture_pov.png"];
[pod_view addSubview:picture_pov];
picture_pov.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *picture_constrant = [NSLayoutConstraint constraintWithItem:text
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:picture_pov
attribute:NSLayoutAttributeBottom
multiplier:1
constant:-27];
[pod_view addConstraint:picture_constrant];
}
Just to add a view that lives in the same parent view as the other two related views.
self.vDist = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 42, 21)];
[self.vDist setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:self.vDist];
NSLayoutConstraint *lc;
lc = [NSLayoutConstraint constraintWithItem:self.vDist attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:20.0];
[self.view addConstraint:lc];
lc = [NSLayoutConstraint constraintWithItem:self.vDist attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:20.0];
[self.view addConstraint:lc];
Then, I create the small squared view on top
/*********************** video view */
self.videoView = [[UIView alloc] initWithFrame:CGRectMake(90, 20, 140, 140)];
[self.videoView setBackgroundColor:[UIColor greenColor]];
[self.videoView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:self.videoView];
// width & height constraints, as UIViews don't have intrinsic constraints
lc = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:140.0];
[self.videoView addConstraint:lc];
lc = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:140.0];
[self.videoView addConstraint:lc];
// center constraint
lc = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
[self.view addConstraint:lc];
// top constraint with its parent view
lc = [NSLayoutConstraint constraintWithItem:self.videoView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:20.0];
[self.view addConstraint:lc];
This squared view shows ok in any device orientation
Now I create the second view on the lower part of the screen. It really doesn't matter its dimensions here as I create a constraint for each size attribute
/*********************** listado view */
self.listadoView = [[UIView alloc] initWithFrame:CGRectMake(5, 248, 310, 100)];
[self.listadoView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.listadoView setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:self.listadoView];
// height and width constraints as UIView don't have intrinsic constraints
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:310];
[self.listadoView addConstraint:lc];
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:300];
// make it weak to be able to squeeze contents
lc.priority = 250;
[self.listadoView addConstraint:lc];
// top with videoView's bottom
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationLessThanOrEqual toItem:self.videoView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:88.0];
[self.view addConstraint:lc];
// top with parent's view top
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:248.0];
[self.view addConstraint:lc];
// center constraint
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
[self.view addConstraint:lc];
trying to replicate here what IB did with the constraints in the other view
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:5];
[self.view addConstraint:lc];
lc = [NSLayoutConstraint constraintWithItem:self.listadoView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:10];
[self.view addConstraint:lc];
Te resulting screens are below with the green square. The vertical green screen shows what I was expecting (see yellow images down below), but in the green horizontal, as you can see, the bottom edge of the listadoView is below the edge of the screen, even though I'm setting all the constraints IB sets for the yellow one, those along with the ones I set in the yellow one too.
Any advice on how to set the constraints?... maybe the order in which they are created has something to do with it?, because there were times when I removed a constraint and it worked, then I re-added that constraint and still worked!. Ran it again with all the constraints, and then stopped working (what?!). And it's been hard to successfully reproduce it in order to file a bug, but it happened.
I've tested this code in XCode 4.6 under iOS5.1 and 6.x and with xCode 5 under iOS6.1.
Found my error. I was setting the listadoView's bottom constraint with its parent view to be 10, that is +10, and it should be -10. With this, the interface shows as with the other .xib (with the yellow square).
I have a simple (complete) example here which seems very odd, but surely im just missing something small... right? Can you help debug the simple code below. This code makes the aView disappear, but when if I put the aLabel in the constraint where the aView is, it works perfect. Why? Thanks for any input, this seems crazy to me.
austin
UIView *aView = [[UIView alloc] initWithFrame:CGRectMake(0, 100, 100, 30)];
aView.backgroundColor = [UIColor redColor];
aView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:aView];
UILabel *aLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 100, 100, 30)];
aLabel.backgroundColor = [UIColor redColor];
aLabel.text = #"Label";
aLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:aLabel];
NSLayoutConstraint *myConstraint =[NSLayoutConstraint
constraintWithItem:aView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0];
[self.view addConstraint:myConstraint];
myConstraint =[NSLayoutConstraint
constraintWithItem:aView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
[self.view addConstraint:myConstraint];
Well, the line aView.translatesAutoresizingMaskIntoConstraints = NO;
is making the view's size zero.. So you got to add these lines of code :
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:aView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:100];
[aView addConstraint:widthConstraint];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:aView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:30];
[aView addConstraint:heightConstraint];
The answer is simple. Certain objects, like UILabels have an intrinsic size based on their contained text, UIViews don't. So, since you didn't set a size for your UIView, its size is 0. You need to add either size constraints, or pin the view to the sides of its superview (or other views in the same view hierarchy).
As an alternative, setting all 4 constraints (left, right, top, bottom) solves the problem as well. Width and height are then adjusted and the UIView will be stretched accordingly. Note that all four constraints must be set.
I have a scroll view with container view(self.tagScrollContentView) inside. That's in Storyboard. Then I generate buttons and place them inside container view with constraints programmatically.
for(NSInteger i = 0; i < allTags.count; i++) {
UIButton *tagBt = [[UIButton alloc] initWithFrame:(CGRect){CGPointZero, tagSize.width + 30, 17}];
[self.tagScrollContentView addSubview:tagBt];
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:tagBt attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:nil multiplier:1.0 constant:tagSize.width + 30]];
if(prevBtRow1)
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:tagBt attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:prevBtRow1 attribute:NSLayoutAttributeRight multiplier:1.0 constant:10.0]];
else
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:tagBt attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.tagScrollContentView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:10.0]];
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:tagBt attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.tagScrollContentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:7.0]];
prevBtRow1 = tagBt;
}
[self.tagScrollContentView addConstraints:constraintsArray];
[self.tagScrollView layoutSubviews];
This code put all buttons in a row depends of their width. All works fine. Then what I need is to enlarge tagScrollContentView to make all buttons become inside this view and not outside of bounds. Then to assign correct content size that is equal to container view to my scroll.
Unfortunately scroll doesn't work correctly. Content size doesn't fit container view.
The key issue is that your contentSize is not getting set because you're not adding that final trailing constraint from the last button to its superview. You can add one more constraint at the end, and your contentSize will be adjusted automatically:
for (NSInteger i = 0; i < allTags.count; i++) {
UIButton *tagBt = [[UIButton alloc] init];
tagBt.translatesAutoresizingMaskIntoConstraints = NO;
[self.tagScrollContentView addSubview:tagBt];
// add all of your constraints
prevBtRow1 = tagBt;
}
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:prevBtRow1
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.tagScrollContentView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:10.0]];
[self.tagScrollContentView addConstraints:constraintsArray];
There are a couple of unrelated issues:
I assume you had a tagBt.translatesAutoresizingMaskIntoConstraints = NO; line that didn't make it into your code sample.
There's no point in doing initWithFrame if you're going to be setting constraints. init is sufficient.
I'd suggest adding a height constraint to your button, too, so its constraints become unambiguous.
By the way, you're adding the button width constraint to the superview. It works either way, but generally you add a constraint to the nearest common parent and for the width constraint, that would be the button itself, not its superview.
Is there a way to avoid image stretching in an UIImageView and aligning it to the bottom at the same time? Basically I want to obtain what would happen if it was possible to combine the UIViewContentModeScaleAspectFit and UIViewContentModeBottom contentModes.
Found the solution on my own. This code resizes the UIImageView to its image's size, and moves it at the bottom of its superview.
CGSize initialImageSize = imageView.size;
CGSize imageSize = imageView.image.size;
CGFloat aspectRatio = imageSize.width / imageSize.height;
CGRect imageFrame = imageView.frame;
if (initialImageSize.width / aspectRatio <= initialImageSize.height) {
imageFrame.size.width = initialImageSize.width;
imageFrame.size.height = imageFrame.size.width / aspectRatio;
} else {
imageFrame.size.height = initialImageSize.height;
imageFrame.size.width = imageFrame.size.height * aspectRatio;
}
CGRect parentFrame = imageView.superview.frame;
imageFrame.origin.y = (parentFrame.origin.y + parentFrame.size.height) - imageFrame.size.height;
imageView.frame = imageFrame;
Constraint based approach
You'll want to constrain the left, right, and bottom sides of the UIImageView while ensuring the ratio of the size is the same as the UIImage that it's displaying.
UIImage *image; // allocate image object here
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[parent addSubview:imageView]; // add imageView to proper view with addSubview: here
UIView *superview = imageView.superview; // match width and bottom of self.superview
CGFloat ratio = image.size.width / image.size.height;
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeLeft multiplier:1.f constant:0.f];
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f];
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeRight multiplier:1.f constant:0.f];
NSLayoutConstraint *ratioConstraint = [NSLayoutConstraint constraintWithItem:imageView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:imageView
attribute:NSLayoutAttributeHeight
multiplier:ratio
constant:1.f];
[imageView addConstraint:ratioConstraint];
[superview addConstraints:#[leftConstraint, bottomConstraint, rightConstraint]];
This will emulate a combination of UIViewContentModeScaleAspectFit and UIViewContentModeBottom but will resize the height of the UIImageView as well.
My solution:
float newwidth;
float newheight;
if (image.size.height>=image.size.width){
newheight=imageview.frame.size.height;
newwidth=(image.size.width/image.size.height)*newheight;
if(newwidth>imageview.frame.size.width){
float diff=imageview.frame.size.width-newwidth;
newheight=newheight+diff/newheight*newheight;
newwidth=imageview.frame.size.width;
}
}
else{
newwidth=imageview.frame.size.width;
newheight=(image.size.height/image.size.width)*newwidth;
if(newheight>imageview.frame.size.height){
float diff=imageview.frame.size.height-newheight;
newwidth=newwidth+diff/newwidth*newwidth;
newheight=imageview.frame.size.height;
}
}
I have found a much easier way to do it.
imageView.contentMode = .Bottom
imageView.clipToBounds = true
This will prevent the image from resizing no matter what frame it is bounded in. And the clipToBounds part is important because it prevents the non resized image from going outside the imageView bounds.
Late to the party, but a simple solution. On the UIImageView:
Use the contentMode property to position the image. (in the current case UIView.ContentMode.bottom)
Use the contentScaleFactor property of the UIImageView to scale the image in the content view.
This will maintain the aspect ratio, just calculate the scale you need. For scaleAspectFit:
yourImageView.contentMode = UIView.ContentMode.bottom
let relWidthScale = yourImage.size.width/yourImageView.frame.width
let relHeightScale = yourImage.size.height/yourImageView.frame.height
yourImageView.contentScaleFactor = min(relWidthScale,relHeightScale)
If the UIImageView changes size, you can update the scale by adding an observer to the bounds property of the UIImageView or its container.