I am an iOS newbie, so please bear with me. I have a blank page with a button centered in it. I want to add another button to the view, just below the centered button. How would I do that? I have added the first button like this -
float x=60, y=200, dy=50;
CGRect frame = CGRectMake(x, y, 200, dy);
UIButton *inboxButton = [[[UIButton alloc] initWithFrame:frame]autorelease];
inboxButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
inboxButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
....
[theView addSubview:inboxButton];
Just calculate the second frame based on the first
CGRect secondFrame = CGRectMake(CGRectGetMinX(frame),
CGRectGetMaxY(frame) + 8.0, // some y padding
CGRectGetWidth(frame),
CGRectGetHeight(frame));
You are not saying if you need that second one centered by itself - I am assuming that you just want it below, left-aligned with the first one:
UIButton *outboxButton = [[[UIButton alloc] initWithFrame:CGRectMake(frame.x,
frame.y + dy + 10.0f,
frame.width,
frame.height)] autorelease];
[theView addSubview:outboxButton];
What I am doing here is simply reusing the horizontal coordinate from the first button. For the vertical coordinate, I am using the original coordinate and add the height and an offset (10.0f) to it. Both, the width and the height are taken from the first button, assuming that they should match in size.
As you will see, there is no way to have this calculation done implicitly, which I assume you actually wanted to find out - that is, by simply supplying some ordering arguments.
Related
I have added UIView at some angle. Now at the run time i want to move that view to up (say 20px).
At start
dragView = [[DragbleView alloc] initWithFrame:CGRectMake(10, 200, 200, 90)];
dragView.transform = CGAffineTransformMakeRotation (-0.663247);
At Run Time
NSLog(#"Before : %#",NSStringFromCGRect(dragView.frame));
[dragView setCenter:CGPointMake(dragView.frame.origin.x, dragView.center.y+20)];
NSLog(#"After : %#",NSStringFromCGRect(dragView.frame));
Console O/p
Before : {{3.4947295778412979, 147.97225037436704}, {213.0105408443174, 194.05549925126593}}
After : {{-103.0105408443174, 167.97225037436704}, {213.0105408443174, 194.05549925126593}}
As you can see it goes to the wrong place. How to place above 20 px
If you read the docs on the UIView frame property, they say:
If the transform property is not the identity transform, the value of
this property is undefined and therefore should be ignored.
So you can't change the frame once you've changed your view's transform. It doesn't work any more.
Instead you should use the view's center property, as #GaryRiches suggests in his answer.
It is doing as you have specified. You would get better results using the center point for both the X and Y:
[dragView setCenter:CGPointMake(dragView.center.x, dragView.center.y + 20)];
Also, as noted below. iOS uses 0,0 as top left, so to move something "up" would require subtracting 20 from the dragView.center.y.
To move UP, it requires to minimize Y location values, whereas you are adding.
Try this:
[dragView setCenter:CGPointMake(dragView.frame.origin.x, dragView.center.y - 20)];
I understand that the question might sound too simple, but only after struggling for several hours with this simple thing am I posting this here. (Yup, I am new to custom drawing and iOS dev in general).
I am trying to draw a coin. It is a gray circle with some text centered on it.
What I have tried so far:
CGFloat *radius = 30;
CGPoint center = self.view.center;
CGRect someFrame = CGRectMake(center.x - radius, center.y - radius,
2 * radius, 2 * radius);
UIView *circularView = [[UIView alloc] initWithFrame:someFrame];
circularView.backgroundColor = [UIColor grayColor];
UILabel *label = [[UILabel alloc] initWithFrame:someFrame];
label.text = #"5";
label.textColor = [UIColor blackColor];
label.textAlignment = NSTextAlignmentCenter;
[circularView addSubview:label];
circularView.clipsToBounds = YES;
circularView.layer.cornerRadius = radius;
[self.view addSubview:circularView];
Have tried this and other variants. But none of it is working. The label is not appearing, and the view is not being drawn in the center of the view as expected in landscape mode.
Is there any better way to do this, using CALayer or Quartz or Core Graphics? As I said, I am new to this.
First things first:
CGFloat *radius = 30;
That...shouldn't really even compile. Or at least there should be one mean warning. That's not what you want. You want to say this:
CGFloat radius = 30;
That asterisk is going to, just, that's bad. You'll know when you want to use a pointer to a primitive value and this just ain't one o' those times.
With that outta the way, you're on the right track, but it looks like you have a misunderstanding about coordinate spaces.
You initialize the circle with the frame someFrame. This is a frame that makes sense in the coordinate space of self.view, which is good, because that's where you're adding it (mostly, see side note below).
But then you set the label's frame to the same thing. Which might be okay, except that you're adding it to the circularView -- not to self.view. Which means that the frame that made sense as a frame for a child of self.view suddenly doesn't make very much sense at all. What you really want is just the following:
// The label will take up the whole bounds of the circle view.
// Labels automatically center their text vertically.
UILabel *label = [[UILabel alloc] initWithFrame:circularView.bounds];
// Center the text in the label horizontally.
label.textAlignment = NSTextAlignmentCenter;
// Make it so that the label's frame is tied to the bounds of
// the circular view, so that if its size changes in the future
// the label will still look right.
label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
The problem that you have now is that label is way off somewhere else, sticking off the edge of circularView (and thus getting clipped) because someFrame doesn't make sense in circularView's coordinate space.
Side note:
This won't work well if self.view.frame.origin isn't CGPointZero. It usually is, but what you really want is the center of the view's bounds, not the center of the view's frame. self.view.center gives you a point in the coordinate space of self.view.superview. It just so happens that this will appear to work as long as self.view.frame.origin is {0, 0}, but to be more technically correct, you should say:
CGPoint center = CGPointMake(CGRectGetMidX(self.view.bounds),
CGRectGetMidY(self.view.bounds));
You can also make circularView remain in the center of the view even as the view's bounds change (for example, if you rotate the device), with the following:
circularView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin;
Yes, typing out autoresizingMasks manually is the worst thing ever, but it's worth it in the long run (actually, in the long run, you'll probably go crazy and make a shorter helper method like me...).
You need to change the origin of the label's frame. When you add the label to the background view, the origin of its frame is relative to its superview.
So it would be something like CGRectMake(center.x - radius, center.y - radius, 2 * radius, 2 * radius) for the background view, CGRectMake(0, 0, 2 * radius, 2 * radius) for the label.
Besides that, you can skip the background view and tint the UILabel's background color.
Your first problem is that you are creating a frame for the view container (circularView) with some non zero x and y. Then you are creating a label and giving it the same frame (with non zero x and y). Then you add the label to the container view. The label's x and y will be relative to the container view's x and y (offset, not centered). If you want the label and the container to share the same location on screen then change it to this:
UILabel *label = [[UILabel alloc] initWithFrame: circularView.bounds];
Another problem is with how simple you are making this you could do it all with the label (ignore the container view).
UILabel *label = [[UILabel alloc] initWithFrame:someFrame];
label.text = #"5";
label.textColor = [UIColor blackColor];
label.textAlignment = NSTextAlignmentCenter;
label.clipsToBounds = YES;
label.layer.cornerRadius = radius;
label.backgroundColor = [UIColor grayColor];
]self.view addSubview:label];
I'm creating compatibility for iOS6 for an app made by someone else. I'm used to using making buttons/UI elements with autoresizing masks but I don't really know how they work when you're creating the button programatically.
For example:
- (UIButton*) createSampleButton {
UIButton* b = createSampleViewButton(CGRectMake(67, 270, 191, 45),
#"btn_shuffle",
#"btn_shuffle_active",
self,
#selector(sampleAction));
b.autoresizingMask = UIViewAutoresizingFlexibleTopMargin;
[self attachButton:b];
return b;
}
How can I change these buttons such that they'll be placed according to some scale/margin instead of arbitrarily choosing points until everything "looks right" ?
I was thinking of something like:
- (UIButton*) createSampleButton {
CGFloat height = self.bounds.size.height;
CGFloat bottomBound = 80;
UIButton* b = createSampleViewButton(CGRectMake(67, height-bottomBound, 191, 45),
#"btn_shuffle",
#"btn_shuffle_active",
self,
#selector(sampleAction));
[self attachButton:b];
return b;
}
This would guarantee me that the button is placed 80 points from the bottom of the screen every time right? Is there a more graceful or purposeful way of doing this?
The masks are the same as when created in IB or code. The thing you want to make sure to do in code though is make sure the frames are set properly proportioned once. In your case, yes you do want UIViewAutoResizingFlexibleTopMargin, and setting the correct y-value on the origin in terms of y = parentView.bounds.size.height - (x points as you described), is all you need to do.
EDIT:
According to your updated question, maybe this will help you. If the button has a constant size, set the frame to that size with CGPointZero as the origin when you create the button. If a UIView owns the button, then put this code in layoutSubviews. If a UIViewController owns the button, replace self.bounds with self.view.bounds and put this in view(Will/Did)LayoutSubviews (Assuming iOS5+).
// Aligning the button at it's current x value, current size, with its bottom border margin pizels from the bottom of the parent view.
CGFloat margin = 10;
CGRect buttonFrame = button.frame;
buttonFrame.origin.y = self.bounds.size.height - buttonFrame.size.height - margin;
button.frame = buttonFrame;
Also, define constant values at the top of the implementation file. Feel free to create convenience methods for readability (if you find this more readable and not doing too much on one line) such as
CGRect CGSetYInRect(CGFloat y, CGRect rect)
...
button.frame = CGSetYInRect(self.bounds.size.height - button.frame.size.height - margin, button.frame);
Use AutoResizing when appropriate to avoid explicit logic in layoutSubviews.
When you move to iOS 6 + only, use AutoLayout.
For example, if we are to draw a 100 x 100 pixel circle on the main view which covers up the whole screen on the iPad, then instead of using initWithFrame like following 2 steps in viewDidLoad:
UINodeView *nodeView = [[UINodeView alloc] initWithFrame:
CGRectMake(x, y, NSNodeWidth, NSNodeHeight)];
nodeView.center = CGPointMake(x, y);
because x and y is more elegantly as self.view.bounds.size.width / 2 to horizontally center the circle, instead of self.view.bounds.size.width / 2 - NSNodeWidth / 2. Is init by a frame first, and then reset the center a good way, or is there a better way, if there is a initWithCenterAndSize?
That's a fine way of doing it :)
I would have gone for generating the positioned frame first to avoid the extra method call but that's just a matter of personal preference :)
If you're using this alot in your app you could make a category on UIView that implements this (warning, untested code :)
- (id)initWithCenter:(CGPoint)point size:(CGSize)size {
CGRect frame = CGRectMake(point.x-size.width/2,
point.y-size.height/2,
size.width,
size.height);
return [self initWithFrame:frame];
}
I usually do this:
UINodeView *nodeView = [[UINodeView alloc] initWithFrame:
CGRectMake(0, 0, NSNodeWidth, NSNodeHeight)];
nodeView.center = CGPointMake(x, y);
It looks nice and clear.
I have a UITableView with a custom header (i.e. I create the UIView myself). I need to tweak the accessibilityFrame of one of the subviews of the view, but I can’t figure out how to set the coordinates of the frame appropriately—they need to be relative to the window, but I’m not sure how to accomplish that.
My code looks like
- (UIView *)tableView:(UITableView *)tableView
viewForHeaderInSection:(NSInteger) section
{
CGRect bounds = CGRectMake(0, 0, [tableView frame].size.width, 48);
UIView *header = [[UIView alloc] initWithFrame:bounds];
UILabel *labelOne = [[UILabel alloc] initWithFrame:
CGRectMake(0, 0, bounds.size.width - 80, 18)];
UILabel *labelTwo = [[UILabel alloc] initWithFrame:
CGRectMake(0, 20, bounds.size.width - 80, 18)];
CGRect frameOne = [labelOne frame];
CGRect frameTwo = [labelTwo frame];
[labelTwo setIsAccessibilityElement:NO];
[labelOne setAccessibilityFrame:CGRectUnion(frameOne, frameTwo)];
// ...
return header;
}
I’ve got two UILabels, which I want to combine into one for the purposes of VoiceOver. I accomplish this by ignoring the second label and extending the frame of the first label to cover the area of the second label. (The second label is immediately below the first.) The problem is getting the frames. If I use the code as shown above, the accessibility frame is the correct size, but is positioned as if the UITableView’s header were in the top left corner of the screen. I tried to modify the code to say
CGRect frameOne = [header convertRect:[labelOne frame] toView:nil];
CGRect frameTwo = [header convertRect:[labelTwo frame] toView:nil];
but the same thing happened. Shouldn’t this latter piece of code convert the UILabels’ frames into window-relative coordinates?
I thought maybe the issue is that when the UIView is created, it doesn’t know where on screen it’s going to be positioned (and as part of a UITableView it may be scrolled all over the place). Is it necessary to implement accessibilityFrame as a message which checks the UIView’s position each time it is called?
There's a helper function that will assist you with doing exactly that: UIAccessibilityConvertFrameToScreenCoordinates. This function takes a CGRect and converts it from a view's coordinate system into screen coordinates.
I don't think it's the timing of when the UIView is created, as I believe the window should be not-nil by the time tableView:viewForHeaderInSection: is called. I think the problem is the receiver of the convertRect:toView: message. Rather than passing this message to header, you should be passing it to [self view].
You're converting from the receivers coordinate system to that of another view, in this case nil or the UIWindow in your app. When header receives this message, you're converting from header's coordinate system to window's coordinate system, but header itself is a subview of [self view]. Instead, you want to ask [self view] to do the conversion, which should take into account any UINavigationBar's, etc.
CGRect frameOne = [[self view] convertRect:[labelOne frame] toView:nil];
CGRect frameTwo = [[self view] convertRect:[labelTwo frame] toView:nil];