iOS: Programatically align button x pixels from bottom of screen - ios

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.

Related

How to Draw a single point line in iOS

I was wondering what is the best way to draw a single point line?
My goal is to draw this line in a tableViewCell to make it look just like the native cell separator.
I don't want to use the native separator because i want to make in a different color and in a different position (not the bottom..).
At first i was using a 1px UIView and colored it in grey. But in Retina displays it looks like 2px.
Also tried using this method:
- (void)drawLine:(CGPoint)startPoint endPoint:(CGPoint)endPoint inColor:(UIColor *)color {
CGMutablePathRef straightLinePath = CGPathCreateMutable();
CGPathMoveToPoint(straightLinePath, NULL, startPoint.x, startPoint.y);
CGPathAddLineToPoint(straightLinePath, NULL, endPoint.x, endPoint.y);
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = straightLinePath;
UIColor *fillColor = color;
shapeLayer.fillColor = fillColor.CGColor;
UIColor *strokeColor = color;
shapeLayer.strokeColor = strokeColor.CGColor;
shapeLayer.lineWidth = 0.5f;
shapeLayer.fillRule = kCAFillRuleNonZero;
[self.layer addSublayer:shapeLayer];
}
It works in like 60% of the times for some reason.. Is something wrong with it?
Anyway ,i'd be happy to hear about a better way.
Thanks.
I did the same with a UIView category. Here are my methods :
#define SEPARATOR_HEIGHT 0.5
- (void)addSeparatorLinesWithColor:(UIColor *)color
{
[self addSeparatorLinesWithColor:color edgeInset:UIEdgeInsetsZero];
}
- (void)addSeparatorLinesWithColor:(UIColor *)color edgeInset:(UIEdgeInsets)edgeInset
{
UIView *topSeparatorView = [[UIView alloc] initWithFrame:CGRectMake(edgeInset.left, - SEPARATOR_HEIGHT, self.frame.size.width - edgeInset.left - edgeInset.right, SEPARATOR_HEIGHT)];
[topSeparatorView setBackgroundColor:color];
[self addSubview:topSeparatorView];
UIView *separatorView = [[UIView alloc] initWithFrame:CGRectMake(edgeInset.left, self.frame.size.height + SEPARATOR_HEIGHT, self.frame.size.width - edgeInset.left - edgeInset.right, SEPARATOR_HEIGHT)];
[separatorView setBackgroundColor:color];
[self addSubview:separatorView];
}
Just to add to Rémy's great answer, it's perhaps even simpler to do this. Make a class UILine.m
#interface UILine:UIView
#end
#implementation UILine
-(id)awakeFromNib
{
// careful, contentScaleFactor does NOT WORK in storyboard during initWithCoder.
// example, float sortaPixel = 1.0/self.contentScaleFactor ... does not work.
// instead, use mainScreen scale which works perfectly:
float sortaPixel = 1.0/[UIScreen mainScreen].scale;
UIView *topSeparatorView = [[UIView alloc] initWithFrame:
CGRectMake(0, 0, self.frame.size.width, sortaPixel)];
topSeparatorView.userInteractionEnabled = NO;
[topSeparatorView setBackgroundColor:self.backgroundColor];
[self addSubview:topSeparatorView];
self.backgroundColor = [UIColor clearColor];
self.userInteractionEnabled = NO;
}
#end
In IB, drop in a UIView, click identity inspector and rename the class to a UILine. Set the width you want in IB. Set the height to 1 or 2 pixels - simply so you can see it in IB. Set the background colour you want in IB. When you run the app it will become a 1-pixel line, that width, in that colour. (You probably should not be affected by any default autoresize settings in storyboard/xib, I couldn't make it break.) You're done.
Note: you may think "Why not just resize the UIView in code in awakeFromNib?" Resizing views upon loading, in a storyboard app, is problematic - see the many questions here about it!
Interesting gotchya: it's likely you'll just make the UIView, say, 10 or 20 pixels high on the storyboard, simply so you can see it. Of course it disappears in the app and you get the pretty one pixel line. But! be sure to remember self.userInteractionEnabled = NO, or it might get over your other, say, buttons!
2016 solution ! https://stackoverflow.com/a/34766567/294884
shapeLayer.lineWidth = 0.5f;
That's a common mistake and is the reason this is working only some of the time. Sometimes this will overlap pixels on the screen exactly and sometimes it won't. The way to draw a single-point line that always works is to draw a one-point-thick rectangle on integer boundaries, and fill it. That way, it will always match the pixels on the screen exactly.
To convert from points to pixels, if you want to do that, use the view's scale factor.
Thus, this will always be one pixel wide:
CGContextFillRect(con, CGRectMake(0,0,desiredLength,1.0/self.contentScaleFactor));
Here's a screen shot showing the line used as a separator, drawn at the top of each cell:
The table view itself has no separators (as is shown by the white space below the three existing cells). I may not be drawing the line in the position, length, and color that you want, but that's your concern, not mine.
AutoLayout method:
I use a plain old UIView and set its height constraint to 1 in Interface Builder. Attached it to the bottom via constraints. Interface builder doesn't allow you to set the height constraint to 0.5, but you can do it in code.
Make a connector for the height constraint, then call this:
// Note: This will be 0.5 on retina screens
self.dividerViewHeightConstraint.constant = 1.0/[UIScreen mainScreen].scale
Worked for me.
FWIW I don't think we need to support non-retina screens anymore. However, I am still using the main screen scale to future proof the app.
You have to take into account the scaling due to retina and that you are not referring to on screen pixels. See Core Graphics Points vs. Pixels.
Addition to Rémy Virin's answer, using Swift 3.0
Creating LineSeparator class:
import UIKit
class LineSeparator: UIView {
override func awakeFromNib() {
let sortaPixel: CGFloat = 1.0/UIScreen.main.scale
let topSeparatorView = UIView()
topSeparatorView.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: sortaPixel)
topSeparatorView.isUserInteractionEnabled = false
topSeparatorView.backgroundColor = self.backgroundColor
self.addSubview(topSeparatorView)
self.backgroundColor = UIColor.clear
self.isUserInteractionEnabled = false
}
}

Horizontally center multiple UIViews

I want to horizontally center a number of UIViews (they happen to be circles) in the master UIView. It will end up basically looking like the dots on the standard Page Control.
I have all the code written to create the circle UIViews I just have no idea how to arrange them horizontally and dynamically at run time.
Essentially I need some kind of horizontal container where I can do this
-(void)addCircle{
[self addSubView:[CircleView init]];
}
And it will auto arrange however many children it has in the center.
I get confused with auto-layout as well from time to time but here is a way how you can do it programmatically: (I assume that you add your circle views to a containerView property of your view controller and you do not add any other views to it.)
Add these two properties to your view controller:
#property (nonatomic) CGRect circleViewFrame;
#property (nonatomic) CGFloat delta;
Initiate those properties with the desired values in your view controller's viewDidLoad method:
// the size (frame) of your circle views
self.circleViewFrame = CGRectMake(0, 0, 10, 10);
// the horizontal distance between your circle views
self.delta = 10.0;
Now we add your "automatic addCircle method":
- (void)addCircleView {
UIView *newCircleView = [self createCircleView];
[self.containerView addSubview:newCircleView];
[self alignCircleViews];
}
Of course we need to implement the createCircleView method...
- (UIView*)createCircleView {
// Create your circle view here - I use a simple square view as an example
UIView *circleView = [[UIView alloc] initWithFrame:self.circleViewFrame];
// Set the backgroundColor to some solid color so you can see the view :)
circleView.backgroundColor = [UIColor redColor];
return circleView;
}
... and the alignCircleViews method:
- (void)alignCircleViews {
int numberOfSubviews = [self.containerView.subviews count];
CGFloat totalWidth = (numberOfSubviews * self.circleViewFrame.size.width) + (numberOfSubviews - 1) * self.delta;
CGFloat x = (self.containerView.frame.size.width / 2) - (totalWidth / 2);
for (int i = 0; i < numberOfSubviews; i++) {
UIView *circleView = self.containerView.subviews[i];
circleView.frame = CGRectMake(x,
self.circleViewFrame.origin.y,
self.circleViewFrame.size.width,
self.circleViewFrame.size.height);
x += self.circleViewFrame.size.width + self.delta;
}
}
This is the most important method which will automatically realign all your subviews each time a new circleView is added. The result will look like this:
Simple steps: append circle to container view, resize container view, center align container view
-(void)addToContanerView:(CircleView*)circle{
circle.rect.frame = CGrectMake(containers_end,container_y,no_change,no_change);
[containerView addSubview:circle];
[containerView sizeToFit];
containerView.center = self.view.center;
}
Assumptions:
containers_end & containers_y you can get from CGRectMax function,
for UIView SizeToFit method check here
To take care of rotation use make sure your Autoresizing subviews are set for left, right bottom and top margin.
You can try using this library. I have used it on several of my projects and so far, it worked really well.
https://github.com/davamale/DMHorizontalView

could not figure out uiview transitions used in yahoo weather app

I was trying to mimic the yahoo weather app screen transition between cities, I couldn't figure out what transition it is. Any clue?
I really appreciate you time.
Thanks
Edit:
I have slideView ctrler which has a subview. The sliderview has an image and the subview has text. When I make a swipe, the text view with text must be moving and dragging way the view ctrler with it at a slower rate and this intern should start dragging in the next view ctrler which is an instance of slider Ctrler.
There is no built-in transition that does this for you (I assume you're talking about the images that are transitioning their frame/center at a different rate than the view itself). You'd probably have to write it yourself. Some basic familiarity with gesture recognizers and view animation is needed.
The basic effect is by simultaneously adjusting the center property for two image views as you change the frame of those views (or their super views). (Or, you can achieve this by having image views whose contentMode is UIViewContentModeCenter and just changing the frame.) I'd suggest you start with some simple tests of the effect and build from there.
For example, I created a scene that has two image views, whose autolayout constraints were defined as follows:
H:|[leftImageView][rightImageView]|
V:|[leftImageView]|
V:|[rightImageView]|
I then defined a width constraint for the leftImageView, and hooked it up to an IBOutlet for that constraint, e.g. leftImageWidthConstraint. I then have a UIPanGestureRecognizer that could handle the gesture, simply changing this leftImageWidthConstraint accordingly (and with auto layout, the rest of the frame is calculated automatically for me from that):
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
CGPoint translate = [gesture translationInView:gesture.view];
static CGFloat width;
if (gesture.state == UIGestureRecognizerStateBegan)
{
width = self.leftImageWidthConstraint.constant;
}
CGFloat newWidth = width + translate.x;
if (newWidth < 0)
newWidth = 0;
else if (newWidth > self.view.bounds.size.width)
newWidth = self.view.bounds.size.width;
self.leftImageWidthConstraint.constant = newWidth;
// if you let go, animate the views to their final position
if (gesture.state == UIGestureRecognizerStateEnded)
{
// if more than half way, set left view's target width to take up full width,
// otherwise set left view's target width to zero
if (newWidth > (self.view.bounds.size.width / 2.0))
newWidth = self.view.bounds.size.width;
else
newWidth = 0;
// animate the changing of the constraint (and thus the `frame`) accordingly
self.leftImageWidthConstraint.constant = newWidth;
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
}
Thus, as I pan across, the two images are centered within their clipped frames:
This is a very basic implementation of the idea. There are, though, a ton of implementation details (custom container vs subviews, autolayout vs not, etc.), so until you answer some of those questions, it's going to be hard to be more specific.
It is not default but i achieved similar to it in my old app
Register gesture on your view and on detection set isFromLeftSide accordingly
Call following. do fine tune this as per your requirements
[self.view addSubview:mySlidingView];
mySlidingView.frame = // set offscreen frame, in the direction you want it to appear from depending on flag isFromLeftSide
[UIView animateWithDuration:8.0
animations:^{
mySlidingView.frame = // desired end location
}];

How to set a UIView's origin reference?

I am creating a UIImageView and adding it in a loop to my view, I set the initial frame to 0,0,1,47 and each passage of the loop I change the center of the image view to space them out.
I am always using 0 as the origin.y
The problem is the origin reference is in the centre of the image view, assuming we was in interface builder, this is equivalent to the image below.
How can I change the reference point in code ?
After reading these answers and your comments I'm not really sure what is your point.
With UIView you can set position by 2 ways:
center – It definitely says it is the center.
frame.origin – Top left corner, can't be set directly.
If you want the bottom left corner to be at x=300, y=300 you can just do this:
UIView *view = ...
CGRect frame = view.frame;
frame.origin.x = 300 - frame.size.width;
frame.origin.y = 300 - frame.size.height;
view.frame = frame;
But if you go one level deeper to magical world of CALayers (don' forget to import QuartzCore), you are more powerful.
CALayer has these:
position – You see, it don't explicitely says 'center', so it may not be center!
anchorPoint – CGPoint with values in range 0..1 (including) that specifies point inside the view. Default is x=0.5, y=0.5 which means 'center' (and -[UIView center] assumes this value). You may set it to any other value and the position property will be applied to that point.
Example time:
You have a view with size 100x100
view.layer.anchorPoint = CGPointMake(1, 1);
view.layer.position = CGPointMake(300, 300);
Top left corner of the view is at x=200, y=200 and its bottom right corner is at x=300, y=300.
Note: When you rotate the layer/view it will be rotated around the anchorPoint, that is the center by default.
Bu since you just ask HOW to do specific thing and not WHAT you want to achieve, I can't help you any further now.
The object's frame includes its position in its superview. You can change it with something like:
CGRect frame = self.imageView.frame;
frame.origin.y = 0.0f;
self.imageView.frame = frame;
If I am understanding you correctly, you need to set the frame of the image view you are interested in moving. This can be done in the simple case like this:
_theImageView.frame = CGRectMake(x, y, width, height);
Obviously you need to set x, y, width, and height yourself. Please also be aware that a view's frame is in reference to its parent view. So, if you have a view that is in the top left corner (x = 0, y = 0), and is 320 points wide and 400 points tall, and you set the frame of the image view to be (10, 50, 100, 50) and then add it as a subview of the previous view, it will sit at x = 10, y = 50 of the parent view's coordinate space, even though the bounds of the image view are x = 0, y = 0. Bounds are in reference to the view itself, frame is in reference to the parent.
So, in your scenario, your code might look something like the following:
CGRect currentFrame = _theImageView.frame;
currentFrame.origin.x = 0;
currentFrame.origin.y = 0;
_theImageView.frame = currentFrame;
[_parentView addSubview:_theImageView];
Alternatively, you can say:
CGRect currentFrame = _theImageView.frame;
_theImageView.frame = CGRectMake(0, 0, currentFrame.size.width, currentFrame.size.height);
[_parentView addSubview:_theImageView];
Either approach will set the image view to the top left of the parent you add it to.
I thought I would take a cut at this in Swift.
If one would like to set a views position on the screen by specifying the coordinates to an origin point in X and Y for that view, with a little math, we can figure out where the center of the view needs to be in order for the origin of the frame to be located as desired.
This extension uses the frame of the view to get the width and height.
The equation to calculate the new center is almost trivial. See the below extension :
extension CGRect {
// Created 12/16/2020 by Michael Kucinski for anyone to reuse as desired
func getCenterWhichPlacesFrameOriginAtSpecified_X_and_Y_Coordinates(x_Position: CGFloat, y_Position: CGFloat) -> CGPoint
{
// self is the CGRect
let widthDividedBy2 = self.width / 2
let heightDividedBy2 = self.height / 2
// Calculate where the center needs to be to place the origin at the specified x and y position
let desiredCenter_X = x_Position + widthDividedBy2
let desiredCenter_Y = y_Position + heightDividedBy2
let calculatedCenter : CGPoint = CGPoint(x: desiredCenter_X, y: desiredCenter_Y)
return calculatedCenter // Using this point as the center will place the origin at the specified X and Y coordinates
}
}
Usage as shown below to place the origin in the upper left corner area, 25 pixels in :
// Set the origin for this object at the values specified
maskChoosingSlider.center = maskChoosingSlider.frame.getCenterWhichPlacesFrameOriginAtSpecified_X_and_Y_Coordinates(x_Position: 25, y_Position: 25)
If you want to pass a CGPoint into the extension instead of X and Y coordinates, that's an easy change you can make on your own.

iOS - how to add one UIButton with respect to another?

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.

Resources