I have created a file which subclasses UIView.
I have declared two other imageViews called background and character respectively.
I want them layered in this order: self->background->character.
Here's what it looks like currently:
Image Here
The red border is the UIView, the green is the background, and the character is in blue (it's already in place which is why you can't see it). I want to move the background+character to the UIView's position.
Here's the code I have so far:
self.bounds = CGRectMake(0.0f, 0.0f, 600.0f, 600.0f);
[self addSubview:self.background];
self.background.bounds = CGRectMake(0.0f, 0.0f, 60.0f, 60.0f);
[self.background addSubview:self.character];
self.character.bounds = CGRectMake(0.0f, 0.0f, 60.0f, 60.0f);
self.character.center = [self.background convertPoint:self.background.center toView:self.character];
I don't know why I have to set self.bounds to 600.0f but that's what makes it the correct size. Any help would be appreaciated.
I have had similar layout issues in the past. The way I was able to get things to line up (if you're not using auto layout) is to use negative x and y coordinates view in front.
Related
I have a circle image with radius 50, and now I want to put the circyle's center the same as my View's center like this:
- (void)drawRect:(CGRect)rect {
UIImage *arrow = [UIImage imageWithPDFNamed:#"circle" atSize:CGSizeMake(50, 50)];
[arrow drawAtPoint:self.center];
}
But drawAtPoint is not drawing at the center.
I want to know how to locate the center of the image? so I can draw it?
From Apple's documentation for UIImage
This method draws the entire image in the current graphics context,
respecting the image’s orientation setting. In the default coordinate
system, images are situated down and to the right of the specified
point. This method respects any transforms applied to the current
graphics context, however.
So offset the center with half the image width and height to draw the center of the image on the center of your view.
- (void)drawRect:(CGRect)rect {
CGSize imageSize = CGSizeMake(50,50);
UIImage *arrow = [UIImage imageWithPDFNamed:#"circle" atSize:imageSize];
[arrow drawAtPoint:CGPointMake(self.center.x-imageSize.width/2., self.center.y-imageSize.height/2.)];
}
You don't need to draw it, just assign it's center to your view's center
arrowsuperview.center = self.center
Try this. This is how you find the center of a subview inside a view
childView.center = CGPointMake(parentView.bounds.size.width / 2, parentView.bounds.size.height / 2);
try this..
UIImageView *circleImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 50, 50)];
circleImageView.image = yourCircleImage;
circleImageView.center = self.view.center;
[circleImageView setContentMode:UIViewContentModeCenter];
[self.view addSubview:circleImageView];
Calculate the center of the UIView using following code and draw the image using the drawAtPoint function.
- (void)drawRect:(CGRect)rect
{
CGPoint ptCenter = {rect.size.width / 2.0f, rect.size.height / 2.0f};
[arrow drawAtPoint:ptCenter];
}
Another simple approach:
You can also achieve the result using storyboard/xib and Auto Layout. Instead of drawing the image in drawRect use an UIImageView to hold your image and position the imageview using Autolayout constraints.
I'm using OpenTok which is a webRTC framework. What I need to do is take the displayed video view and crop it to a circle. Problem is, since this video avatar view will be placed in a view with a clear background, I can't just use a mask as shown in this S.O. question:
Cut Out Shape with Animation
I've also tried to use layer.radius in a UIView category:
-(void)setRoundedViewToDiameter:(float)newSize;
{
CGPoint saveCenter = self.center;
CGRect newFrame = CGRectMake(self.frame.origin.x, self.frame.origin.y, newSize, newSize);
self.frame = newFrame;
self.layer.cornerRadius = newSize / 2.0;
self.center = saveCenter;
}
And then applied like so:
- (void) setUserVideoView:(UIView *)view {
[view setRoundedViewToDiameter:[WSUserView dimForUserAvatar:_sizeIndex]];
self.userVideo = view;
[self.userVideo setRoundedViewToDiameter:[WSUserView dimForUserAvatar:_sizeIndex]];
[self addSubview:self.userVideo];
[self sendSubviewToBack:self.userVideo];
[self layoutSubviews];
}
But it's still an uncropped rectangle. Here's the portion of the video view. I'm showing user image avatars at first, but then when a video stream connects I want to replace the image with the video view, but as a circle. The left image is the stream view that I need make a circle.
Also, here's the inspector view of the video view I'm trying to crop. As you can see, it's a OTGLKVideoView class.
Migrated from my comment:
You should set self.layer.masksToBounds = YES because this ensures that the layer's sublayers are clipped with the corner radius too. I'm assuming that the problem is arising because the ever-changing sublayer that is updated whenever the video's frame changes is thereby ignoring the corner radius.
More details can be found through this answer which solves a similar problem: https://stackoverflow.com/a/11325605/556479
I wrote a small sample program to better understand the meaning of UIViews property bounds. The program has one UIViewController. I put a custom view inside it's root view, and override drawRect: on that custom view. And I add a button to "Change Bounds". Here's the screenshot.
Here's drawRect:. It draws a green dot at (x=50,y=60), and a gray rectangle over self.bounds.
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(c, 0, 1, 0, 0.5); // green
CGContextFillRect(c, CGRectMake(50, 60, 10, 10));
CGContextSetRGBStrokeColor(c, 0, 0, 0, 0.5); // gray
CGContextSetLineWidth(c, 10.0);
CGContextStrokeRect(c, self.bounds);
Now I click the button which adds (20,40) to the bounds.origin of the custom view, like this:
CGRect bs = _testView1.bounds;
bs.origin.x += 20.0;
bs.origin.y += 40.0;
_testView1.bounds = bs;
[_testView1 setNeedsDisplay];
Now, here's what the screen looks like:
You see the view itself has not moved on screen, but the green dot has moved. I think to myself: I get it, the bounds changes the portion of the view`s world that is mapped to it's area of the screen.
Next expermient: I'll add a subview. In awakeFromNib I add a label, as a child of the custom view, like so:
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(20, 50, 100, 100)];
label.text = #"Test";
[self addSubview:label];
At first, the screen is as expected:
But when I change the bounds, as before, things move differently.
It does not translate the view's coordinate system, like before. Why is that? Does adding to self.subviews change the meaning of self.bounds? If so, where is that documented?
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
}
}
Just started working with Core Graphics and I probably got no idea what's going on.
In the following code I'm trying to create a small rounded translucent black square overlaid on top of the UINavigationController, but so far nothing showed up...
UIView *notificationView = [[UIView alloc] initWithFrame:[[[self navigationController] view] frame]];
CGRect rect = CGRectMake(self.view.frame.size.width / 2 - 50, self.view.frame.size.height / 2, 100, 100);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
[[UIColor colorWithWhite:0 alpha:0.5] setFill];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:10];
[path fill];
[notificationView setNeedsDisplay];
[[[self navigationController] view] addSubview:notificationView];
Option 1 (the most design-friendly one)
UIViews are not really meant for other objects to draw into them. It makes much more design sense to subclass UIView and let it do its own drawing in drawRect. That way, you don't have to paste so much code every time you want to use a notification view.
Option 2 (the easiest one, and probably best)
If you just want a translucent black rounded rectangle (I'm assuming for a loading indicator), you can do it much more easily by creating the UIView at the size you want and centering it in the view. Set its background color to the translucent color, [UIColor colorWithWhite:0.0 alpha:0.5]. Finally, add the line
notificationView.layer.cornerRadius = 10.0;
You may also need to put #import <QuartzCore/QuartzCore.h> in your header file, since this is a CALayer trick.
Option 3 (the roundabout one)
If you really want to do it the way you're already doing, change the notificationView to a UIImageView, then set the frame of the view to be the size of the black rounded rect. Then add this after you fill the path:
UIImage *indicatorImage = UIGraphicsGetImageFromCurrentImageContext();
notificationView.image = indicatorImage;
You don't need to call setNeedsDisplay anymore.
Hopefully one of these sounds good to you!