Adding subviews programmatically positions them weirdly - ios

I have a view in which I want to add multiple subviews, however when I add them, they get positioned in positions where I didn't set the frame. The x coordinate is correct, but the y is quite off.
Using the Interface Builder it went quite smooth, just drag them in and send the correct frame and origin. However I don't appear to be able to set the origin; I get expression is not assignable when I try view.frame.origin = CGPointMake(x, y), and setting the x and y coordinates directly gives me the same error.
Does this happens because subviews cannot overlap programmatically without setting a special attribute (that I'm missing)?
Edit: The views are being set in the initWithStyle method of a UITableViewCell.
Edit 2: Added code within initWithStyle method.
// Initialize images
self.imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image"]];
self.anImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"anImage"]];
self.anotherImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"anotherImage"]];
// Set imageview locations
self.imageView.frame = CGRectMake(0, 0, 300, 54);
self.anImageView.frame = CGRectMake(20, 53, 16, 52);
self.anotherImageView.frame = CGRectMake(179, 43, 111, 53);

To avoid expression is not assignable, you have to set the entire frame at once, either using
view.frame = CGRectMake(x, y, width, height)
or
CGRect frame = self.view.frame;
frame.origin.x = newX;
self.view.frame = frame;

Most likely you are setting the frames in the viewDidLoad method. The problem here is that you are setting the frame before the viewControllers frame has been resized based on the constraints of the app.
Try moving your frame setting into the method viewWillAppear and see if that fixes your problem.
Edit: Because you are doing this in a cell, and not in a viewController you would do something like the following:
In initWithStyle:reuseIdentifier
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
self.customView = [[UIView alloc] initWithFrame:CGRectZero];
}
return self;
}
Then override layoutSubviews to actually set the views frame
- (void)layoutSubviews
{
[super layoutSubviews];
self.customView.frame = CGRectMake(x, y, width, height);
}
As far as the "expression is not assignable" warning, this is because you cannot just set a views origin without setting the height and width. Use:
view.frame = CGRectMake(x, y, width, height);
If you want to keep the same width and height without having to hard code it do something like
view.frame = CGRectMake(x, y, view.frame.size.width, view.frame.size.height);

Related

Issue with subviews overlapping

I'm trying to create a custom view which containes several images. I do that by adding them programmatically. The problem is that those subviews overlap each other and I can't find the way to change that. The only solution I can see is doing something like setting frames for each new image programmatically. I would be grateful if someone could tell me what is the best way to solve this issue.
for (id image in self.images) {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self.imageViews addObject:imageView];
[self addSubview:imageView];
}
If you wanna make your customView like UICollectionView you need a UIScrollView and add your subviews in it. Everytime when you add a subview change frame location so it could be something like this:
int xPosition = 0;
int yPosition =0;
for (id image in self.images) {
if (xPosition>self.view.frame.size.width) {
//give size to every imageView and every time in loop change the location
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(xPosition, yPosition, 50, 50)];
imageView.image = image;
yPosition = yPosition + 50;
[self.view addSubView:imageView];
}
else {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(xPosition, yPosition, 50, 50)];
imageView.image = image;
xPosition = xPosition + 50;
[self.view addSubView:imageView];
}
}
Without using Interface Builder your only real options are to change the frame or the center.
imageView.frame = CGRectMake(x coord, y coord, width, height);
This method lets you resize and move whereas changing the center lets you do just that, move the center of the view.
or
imageView.center = CGPointMake(x coord, y coord);
Or as recommended add constraints.

Why isn't my code moving image position?

I want an image to automatically align to the top, I figure the only want to do this is through making a frame that positions it that way. After doing some research, I can't seem to figure out why this code doesn't move the image at all... I have substituted many values in the topPadding and leftPadding variables..
- (void)constructImageView:(UIImage *)image {
CGFloat topPadding = 20.f;
CGFloat leftPadding = 30.f;
CGRect frame = CGRectMake(leftPadding, topPadding, image.size.width, image.size.height);
self.imageView = [[UIImageView alloc] initWithFrame:frame];
self.imageView.image = image;
[self addSubview:self.imageView];
}
Are you using Autolayout? If yes, setFrame: does not work properly with autolayout. If thats the case, then your options are:
Use this: self.view.translatesAutoresizingMaskIntoConstraints = YES
Write your own constraints instead of setting the view's frame

iOS - center content of UIView

I am trying to create UIView with image and number. I want to center them. For now I have this code:
CGRect likesRect = CGRectMake(0,152, screenWidth / 2, 30);
MARoundedRect *likesRoundedRect = [[MARoundedRect alloc] initWithFrame:likesRect];
UIView *likesView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 30)];
likesView.center = CGPointMake(screenWidth / 4, 20);
UIImageView *likeImage = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 20, 20)];
likeImage.image = [UIImage imageNamed:#"favorite.png"];
UILabel *likesLabel = [[UILabel alloc]initWithFrame:CGRectMake(30, 0, screenWidth / 2, 20)];
[likesLabel setTextColor:[UIColor whiteColor]];
[likesLabel setBackgroundColor: [UIColor clearColor]];
[likesLabel setText:[actualPlace.Favorites stringValue]];
[likesView addSubview:likeImage];
[likesView addSubview:likesLabel];
[likesRoundedRect addSubview:likesView];
[viewController.view addSubview:likesRoundedRect];
For now it's working good but I am setting width of likesView to exact value and I guess this goes bad when I don't have number with one or two letters but more. How can I fix this? How to center content of UIView if there are two or more components with side by side? Or how can I set UIView to dynamically change width by content? Thanks
I am not writing full code but you try with following code,
CGRect likesRect = CGRectMake(0,152, screenWidth / 2, 30);
MARoundedRect *likesRoundedRect = [[MARoundedRect alloc] initWithFrame:likesRect];
CGSize likeViewSize = CGSizeMake(50,30);
UIView *likesView = [[UIView alloc] init];
likesView.frame = CGRectMake(likesRoundedRect.frame.size.width/2 - likeViewSize.width/2,likesRoundedRect.frame.size.height/2 - likeViewSize.height/2,likeViewSize.width,likeViewSize.height);
I have centered the likesView on its parentview. you have to do same thing for label and imageview if you want to center them on their parentview (likesView)
UIView and its subclasses has a property called center :
CGPoint center1 = CGPointmake(self.view.bounds.size.width/2,
self.view.bounds.size,height/2) ;
subview.center = center1;
The following discussion is from the Apple documentation on UIView:
#property(nonatomic) CGPoint center
Discussion
The center is specified within the coordinate system of its superview and is measured in points. Setting this property changes the values of the frame properties accordingly.
Changing the frame rectangle automatically redisplays the receiver without invoking the drawRect: method. If you want the drawRect: method invoked when the frame rectangle changes, set the contentMode property to UIViewContentModeRedraw.
Changes to this property can be animated. Use the beginAnimations:context: class method to begin and the commitAnimations class method to end an animation block.
Availability
Available in iOS 2.0 and later.
See Also
#property frame
#property bounds
#property transform
Related Sample Code
AVLoupe
Handling Touches Using Responder Methods and Gesture Recognizers
iAdInterstitialSuite
iAdSuite
RosyWriter
Declared In
UIView.h

UIImageView and autolayout

I have a view that is set up nicely using autolayout. The view contains a series of labels stacked from top to bottom. I am allowing the intrinsic size of these labels to determine the size of the view.
The final step is to add a background from an image. I started by trying the colorWithPatternImage method on UIColor but this isn't quite what I am looking for. I do not want to tile the image, and I can not guarantee it will always be larger than the intrinsic size of the view.
Similarly, adding a uiImageView to the view itself doesn't quite work. The view will expand to accommodate the image when I want to keep the intrinsic size based on the labels.
I guess what I am looking for is the following.
1) The background should have no effect on the size of the view.
2) The image should be scaled to fill the view but in it's original aspect ration (so cropping edges if necessary).
Any ideas appreciated.
In my case, I needed it for a UIImageView inside a dynamically-sized view in a UITableViewCell, but the image refused to shrink below its instristic size and instead worked as a minimum-size constraint for the superview. The only way I could get it ignore the intristic size is by lowering the priority at which it is enforced, right after creating the cell:
[imageView setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisHorizontal];
[imageView setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisVertical];
After this, all my constraints magically started working. In the OP's case, setting UIViewContentModeScaleAspectFill is also required, as per Mundi's answer.
In Interface Builder, add a UIImageView as the first subview to the view. Make sure its size always matches the view.
Then, in Interface Builder or code, set the contentMode:
backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
Here's how I would approach this. Hopefully it helps. :)
CGRect contentFrame = CGRectMake(0, 0, self.view.frame.size.width, 0); // This will be the frame used to create the background image view.
UIEdgeInsets contentInsets = UIEdgeInsetsMake(20, 20, 20, 20); // The margins by which the labels will be inset from the edge of their parent view.
CGFloat labelHeight = 21;
CGFloat verticalGap = 8; // The vertical space between labels
CGFloat y = contentInsets.top;
int numberOfLabels = 10;
for (int i = 0; i < numberOfLabels; i++) {
CGRect frame = CGRectMake(contentInsets.left, y, self.view.frame.size.width - (contentInsets.left + contentInsets.right), labelHeight);
UILabel *label = [[[UILabel alloc] initWithFrame: frame] autorelease];
// customize the label here
[self.view addSubview: label];
contentFrame = CGRectUnion(contentFrame, label.frame);
y += labelHeight + verticalGap;
}
contentFrame.size.height += contentInsets.bottom;
UIImageView *backgroundImageView = [[[UIImageView alloc] initWithFrame: contentFrame] autorelease];
[backgroundImageView setClipsToBounds: YES];
[backgroundImageView setContentMode: UIViewContentModeScaleAspectFill];
[backgroundImageView setImage: [UIImage imageNamed: #"background_image.png"]];
[self.view insertSubview: backgroundImageView atIndex: 0];

When would a UIView's bounds.origin not be (0, 0)?

When would an UIView's bounds.origin not be (0, 0)?
This paragraph was helpful to me:
IMPORTANT!! Bounds X and Y, the origin, are for moving inside the
View. For eample X+5, moving 5pix to the left of the frame's origin
meaning draw all content within this View to the left 5pix of frame's
origin. It doesn't do anything to itself, it is what being drew on it
that get affected.
But it describes only the case when I had set the value of bounds.origin myself.
In what other cases the value of bounds.origin != (0, 0)?
View's frame determines its location in superview. View's bounds determines its subviews locations. That means, if you change view's bounds, its location won't be changed, but all of its subviews location will be changed.
Positive width and height is like you draw a view from upper-left to bottom-right, while negative value is from bottom-right to upper-left. So
frame1 = CGRectMake(100, 100, -50, -50)
is totally identical with
frame2 = CGRectMake(50, 50, 50, 50).
And in fact, if you init a view with frame1, it will AUTOMATICALLY CHANGED to frame2.
But the bounds.origin of the views are not identical. Bounds.origin indicates the point that you "draw" the view, so all subviews frames will originate at this point.
For example, in Landscape iPhone 6, we have:
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 275, 275)];
leftView.backgroundColor = [UIColor greenColor];
[self.view addSubview:leftView];
UIView *rightView = [[UIView alloc] initWithFrame:CGRectMake(667-50, 375-50, -275, -275)];
rightView.backgroundColor = [UIColor blueColor];
[self.view addSubview:rightView];
And we got:
We will find that rightView's frame is automatically changed to positive value, which is (342, 50, 275, 275), but its bounts.origin = (-275,-275).
And we add subviews:
UIView *leftSubview = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
leftSubview.backgroundColor = [UIColor grayColor];
[leftView addSubview:leftSubview];
UIView *rightSubview= [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
rightSubview.backgroundColor = [UIColor grayColor];
[rightView addSubview:rightSubview];
So the bounds makes rightView's subview follows the origin which we init rightView.
If we change the bounds of rightView equals to leftView:
rightView.bounds = leftView.bounds;
Then the two subViews location is the same, we lost the information that rightView's width and height are negative.
And we change the bounds of leftView instead of rightView:
CGRect bounds = leftView.bounds;
bounds.origin = CGPointMake(50, 50);
leftView.bounds = bounds;
We can see, its subview's frame.origin is offset by bounds.origin(using minus, not plus).
To conclude:
view.bounds determines all its subview's location(offset by bounds.origin), while bounds will not affect its own location in its superview.
If you init a view with negative width and height, it will automatically changed to positive(which won't change the location), but its bounds.origin indicates the point that you start to "draw" the view.
A UIScrollView's bounds.origin will not be (0, 0) when its contentOffset is not (0, 0).
The bounds.origin will be negative if you initialize a view with negative width/height.
For example, if you did
UIView* v = [[UIView alloc] initWithFrame:CGRectMake(5, 5, -10, -20)];
the frame would be:
origin = {
x = -5,
y = -15
},
size = {
width = 10,
height = 20
}
bounds:
origin = {
x = -10,
y = -20
},
size = {
width = 10,
height = 20
}
center:
x = 0,
y = -5
try it for yourself!
(edited again because I can’t delete my original answer after it was accepted—credit for this goes to ian, who posted a more thorough answer below:)
In most cases this won’t happen. If you initialize your view with a negative width and/or height, you’ll get an origin with a negative X of the width and/or negative Y of the height.

Resources