I'm using [BJImageCropper]1 and trying to figure out how to remove the squares effect around the crop area. I want to have a full area of black alpha around the crop area. Sounds easy, so the only thing I changed in the above project was changing the alpha to 0.75 in BJImageCropper.m:
- (UIView*)newEdgeView {
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor blackColor];
view.alpha = 0.75; //(was 0.5)
[self.imageView addSubview:view];
return view;
}
My problem is that there's a black line that's following me around in the top right corner of the crop area. matter what I changed in the code - I can't get rid of.
Related
I have a custom container view with an orange backgroundColor.
In this custom view I draw lines by overriding drawRect. This works great until I try and draw lines over subviews.
Here are some screenshots that correlate with code edits to illustrate the issue I'm facing:
^ This image shows my custom view with self.graphBackgroundView present, but without an explicit backgroundColor being set. My line from my drawRect is visible. This is good.
^ This image shows my custom view with self.graphBackgroundView present, but WITH an explicit backgroundColor being set. It's as if my green subview's z-index is higher than my drawRect's z-index, or something.
^ Finally, this image shows my custom view with self.graphBackgroundView present, an explicit backgroundColor being set (still green), but WITH the self.graphBackgroundView.layer.opacity set to 0.25. Here we can see the drawRect line again but it's not quite right, we really wish the line would draw on top of the view entirely, not underneath.
The real issue is what we see in the green screenshot. We want the opaque green subview, we just want our white line to draw on top of it.
Any help much appreciated!
Here's the code:
- (void)drawRect:(CGRect)rect
{
// GET CONTEXT
CGContextRef context = UIGraphicsGetCurrentContext();
// INIT PATH
CGMutablePathRef path = CGPathCreateMutable();
// CONFIG PATH
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColor(context, CGColorGetComponents([UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0].CGColor)); // 4 color component white for use with CGColorGetComponents
// ESTABLISH STARTING POINT
CGPathMoveToPoint(path, NULL, 0.0, 100.0);
// GRAPH NEXT POINT
CGPathAddQuadCurveToPoint(path, NULL, 120.0, 160.0, 120.0, 160.0);
// GRAPH NEXT POINT
CGPathAddQuadCurveToPoint(path, NULL, 130.0, 170.0, 130.0, 170.0);
// ADD THE PATH
CGContextAddPath(context, path);
// DRAW
CGContextDrawPath(context, kCGPathStroke);
}
- (id)init
{
self = [super init];
if (self)
{
// INIT UI ELEMENTS
self.graphBackgroundView = [[UIView alloc] init];
// INIT AUTO LAYOUT VIEWS DICT
self.viewsDictionary = [NSMutableDictionary new];
[self.viewsDictionary setObject:self.graphBackgroundView forKey:#"graphBackgroundView"];
// TURN ON AUTO LAYOUT
self.graphBackgroundView.translatesAutoresizingMaskIntoConstraints = NO;
// ESTABLISH VIEW HIERARCHY
[self addSubview:self.graphBackgroundView];
// LAYOUT
// graphBackgroundView
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[graphBackgroundView]-(0)-|" options:0 metrics:0 views:self.viewsDictionary]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(topMargin)-[graphBackgroundView]-(bottomMargin)-|" options:0 metrics:#{#"topMargin":self.topMargin,#"bottomMargin":self.bottomMargin} views:self.viewsDictionary]];
// CONFIG
// self
self.backgroundColor = [UIColor orangeColor]; // My drawRect code DOES draw on top of this color
// graphBackgroundView
self.graphBackgroundView.backgroundColor = [UIColor greenColor]; // My drawRect code does NOT draw on top of this color
//self.graphBackgroundView.layer.opacity = 0.25; // <-- If I uncomment this I can kind of see the line I'm drawing underneath it via the effects of transparency
}
return self;
}
It's as if my green subview's z-index is higher than my drawRect's z-index, or something.
Well, that's exactly the case. If you have a superview and its subviews, the subviews are drawn in front of the superview. That is part of what it means to be a subview. The only surprise here is that you're surprised.
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 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!
I have a view with a border of 10 pixels drawn on the method.
I need to update the border color and I use [self setNeedsDisplay] to make it redraw
the view.
Since I need to update only the border I want to use : [self setNeedsDisplayInRect:rect] so it will draw only the border.
How can I get a rect of only the border with out the other areas of the view?
Thanks
Shani
You can't because a CGRect is rectangle, so it is a convex shape that can't have holes in it.
But you can decompose the border into four rectangles and call [self setNeedsDisplayInRect:rect] four times.
Also, if you import QuartzCore, you can probably use the property borderColor of the view's layer:
#import <QuartzCore/QuartzCore.h>
// ...
view.layer.borderWidth = 10;
view.layer.borderColor = [UIColor redColor].CGColor;
// And to change it later
view.layer.borderColor = [UIColor greenColor].CGColor;
You could get four CGRects around each part of the border (top, right, bottom, and left) and call the method four times with each of them.