How to draw on a UIView with core graphics? - ios

I can't figure out why I can't draw on the UIView layer. Is that possible?
My example code i am using:
#implementation Menu
UIView *window;
UIWindow *mainWindow;
CGSize viewwidth;
viewwidth=[[UIScreen mainScreen] bounds].size;
// viewwidth.width or viewwidth.height
//My UIView
window = [[UIView alloc]initWithFrame:CGRectMake(0, 0, viewwidth.width, viewwidth.height)];
window.backgroundColor = [UIColor whiteColor];
window.layer.opacity = 1.0f;
[window setNeedsDisplay];
[mainWindow addSubview: window];
My method drawRect for drawing:
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGRect rectangle = CGRectMake(0, 0, 320, 100);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 0.0); //this is the transparent color
CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 0.5);
CGContextFillRect(context, rectangle);
CGContextStrokeRect(context, rectangle); //this will draw the border
}
Am I doing something wrong, the problem is i get a empty white screen?

When you create a project and chose to use Storyboards you will set a ViewController to your Main.storyboard in Interface Builder.
There are differences between iOS Apps that use SceneDelegates and those who do not (iOS before 13.0). But your chosen ViewController will be assigned to be used from within your Main Window/Scene when the App will be loaded. In AppDelegate.m (before iOS 13) or SceneDelegate.m (starting iOS 13) you could directly access or even assign this yourself to the self.window.rootViewController property.
Do to inheritance of UIViewController which come with a basic UIView ready to work with under self.view (in this example inside ViewController.m) you can place more Subviews to that property with [self.view addSubview:yourOtherView].
-(void)drawRect:(CGRect)rect is a method that belongs to UIView and will be called when the view is set to update or layout. But it is important to know that this is not the only method where you can place drawing code. UIView:drawRect can be avoided to lower the Energy impact and workload you app is consuming when you don't need repeatedly redrawing the graphics. Also the workload is heavy influenced to be lower when you are able to lower the CGRect size to draw inside.
One word to value naming.. you will have less confusing code when values are named after the content they belong to. So naming a property "window" which is actually a UIView will stress you when your project becomes more complex. So you created a local value with name mainWindow while window is already existing. When doing so you will have to assign your mainWindow to the apps UIWindow yourself to make it available. And the UIView you named window can confuse you while _window or self.window is already existing as part of the AppDelegate or SceneDelegate. And as mentioned above you have a UIView that belongs to the UIViewController you assigned in Storyboard or in code.
Your drawing code is valid. It may be invisible to you because of transparency you choose to go for.
below some code to compare to for your ViewController.
#import "ViewController.h"
#import "MyGraphicView.h"
#interface ViewController ()
#property (nonatomic) MyGraphicView *graphicView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
_graphicView = [[MyGraphicView alloc] initWithFrame:self.view.frame];
[self.view addSubview:_graphicView];
}
#end
and a MyGraphicView.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyGraphicView : UIView
#end
NS_ASSUME_NONNULL_END
and corresponding MyGraphicView.h
#import "MyGraphicView.h"
#implementation MyGraphicView
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
// This method is called when a view is first displayed or when an event occurs that invalidates a visible part of the view.
- (void)drawRect:(CGRect)rect {
CGRect rectangle = CGRectMake(0, 0, 320, 100);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 0.0);
CGContextSetRGBStrokeColor(context, 0.0, 1.0, 0.0, 0.5);
CGContextFillRect(context, rectangle);
CGContextStrokeRect(context, rectangle);
}
#end

Related

In my custom UIView, setting a subview's backgroundColor causes it to cover up my drawRect drawings (CGContextRef, CGMutablePathRef,etc). How to fix?

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.

Not doing CG drawings correctly

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!

Draw a Radiant Gradient: <Error>: CGContextDrawPath: invalid context 0x0

I'm developing an iOS 5.0+ app with latest SDK.
I'm very very new on CoreGraphics and I don't know how to draw a radiant gradient on a CALayer.
I have found that I have to use CGContextDrawRadialGradient to draw a radiant gradient.
Searching on Google, I see that I have to add the radiant gradient to CALayer's content, but to draw it I need a CGContext, and I don't know how to get this CGContext.
Do you know how can I do it?
I have found this tutorial, but it also uses CGContext.
My code is this:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIView *testView;
#end
Implementation:
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
- (void)drawRadiantGradient;
#end
#implementation ViewController
#synthesize testView = _testView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self drawRadiantGradient];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)drawRadiantGradient
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat redBallColors[] = {
1.0, 0.9, 0.9, 0.7,
1.0, 0.0, 0.0, 0.8
};
CGFloat glossLocations[] = {0.05, 0.9};
CGGradientRef ballGradient = CGGradientCreateWithColorComponents(colorSpace, redBallColors, glossLocations, 2);
CGRect circleBounds = CGRectMake(20, 250, 100, 100);
CGPoint startPoint = CGPointMake(50, 270);
CGPoint endPoint = CGPointMake(70, 300);
CGContextDrawRadialGradient(context, ballGradient, startPoint, 0, endPoint, 50, 0);
CGContextAddEllipseInRect(context, circleBounds);
CGContextDrawPath(context, kCGPathStroke);
}
I want to create a CALayer, draw a radiant gradient, and add this CALayer to _testView.
You would either draw into your own context (for example an image context that you created using UIGraphicsBeginImageContextWithOptions()
or
become the delegate of that layer and draw into the layers graphics context using drawLayer:inContext:
It sounds to me like you want to do the second option. You would create the CALayer and set yourself as the delegate. Then implement drawLayer:inContext: and use the context the context that is passed as an argument.
Setup
CALayer *yourLayer = [CALayer layer];
// other layer customisation
yourLayer.delegate = self;
And draw
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
// Check that the layer argument is yourLayer (if you are the
// delegate to more than one layer)
// Use the context (second) argument to draw.
}
The UIGraphicsGetCurrentContext() only return a valid context when called from sprecific methods or of you created a new one f.e. by calling UIGraphicsBeginImageContext().
So what you need to do, is either subclass a UIView and overwrite it's drawRect: method with the code in your drawRadiantGradient method. Or you could use the delegate methods provided by the CALayerDelegate protocol. Just set the layer's delegate to your vc and implement the drawLayer:inContext: method. This could look like this:
in your .h file:
#interface MyVC : UIViewController
#property (weak, nonatomic) IBOutlet UIView *drawingView;
/...
and in your .m file:
- (void)viewDidLoad
{
[super viewDidLoad];
self.drawingView.layer.delegate = self;
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
[self drawRadiantGradientInContext:context];
}
Make a custom subclass of UIView that draws the gradient in its drawRect: method, and have your VC create a radial-gradient view and add that as a subview of its view.

UIView drawRect EXC_BAD_ACCESS on Recreate a Custon UIView

I having some problems with drawRect and create a new instance of a UIView custom class.
My question is: How can i use of drawrect?
I created a UIViewController with a UIScrollView and inside this UIScrollView about 50 UIViews. Each UIView create inside 3 elements (Custom UIViews) each one with a drawrect:
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextClearRect(c, self.bounds);
CGContextSetStrokeColorWithColor(c, color.CGColor);
CGContextSetLineCap(c, kCGLineCapRound);
CGContextSetInterpolationQuality(c, kCGInterpolationHigh);
CGContextSetLineWidth(c, thickness);
CGContextAddArc(c, self.frame.size.width/2, self.frame.size.height/2, radius, angleIni*M_PI/180, angleEnd*M_PI/180, 0);
CGContextStrokePath(c);
}
At first it works like a charm, but when i try to remove this UIViewController and Create it again I'm getting a EXC_BAD_ACCESS.
If I remove the drawrect code everything begin to work again with no EXC_BAD_ACCESS. So I conclued tha my problem ismy drawrect method.
Is there some right way to true remove my UIviewController? On dealloc I'm removing all my UIViews inside UIScrollView and setting it to nil, besides I'm setting UIScrollView to nil too, like:
for (UIView *item in MyUICscrollView.subviews)
{
[item removeFromSuperview];
item = nil;
}
MyUICscrollView = nil;
Had someone the same issue?

Custom Drawing with Xib file present

I have a view that I want to add some custom drawing to.
I know how to do this with a View that isn't connected to a Nib/Xib file
you write the drawing code in the -drawRect: method.
But if I init the view using
[[MyView alloc] initWithNibName:#"MyView" bundle:[NSBundle mainBundle]];
-drawRect: of course doesn't get called. I tried doing the below code in -viewDidLoad
CGRect rect = [[self view] bounds];
CGContextRef ref = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ref, 2.0);
CGContextSetRGBStrokeColor(ref, 1.0, 1.0, 1.0, 1.0);
CGContextSetRGBFillColor(ref, 0, 0, 0, 0);
CGContextAddRect(ref, CGRectMake(1, 1, rect.size.width - 10, rect.size.height - 10));
CGContextStrokePath(ref);
CGContextDrawPath(ref, kCGPathFillStroke);
But nothing get drawn. Any ideas?
I think the issue is that you're treating a view and its view controller as interchangeable. For example, there's no -[UIView initWithNibName:bundle:] method — that's a UIViewController method.
Furthermore, a view isn't really like a "canvas" for drawing. A view will be asked to draw itself; in general, it won't be drawn into from "outside."
So:
Rename your subclass of UIViewController from MyView to MyViewController.
Create a new UIView subclass named MyView.
Add a -drawRect: method to your new MyView class that does the drawing you want.
Finally, set the Custom Class of your view controller's view in Interface Builder to MyView using the Identity Inspector.
For example, you should be able to use this for your -[MyView drawRect:] implementation:
- (void)drawRect:(CGRect)rect {
CGRect bounds = [[self view] bounds];
CGContextRef ref = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ref, 2.0);
CGContextSetRGBStrokeColor(ref, 1.0, 1.0, 1.0, 1.0);
CGContextSetRGBFillColor(ref, 0, 0, 0, 0);
CGContextAddRect(ref, CGRectMake(1, 1, bounds.size.width - 10, bounds.size.height - 10));
CGContextStrokePath(ref);
CGContextDrawPath(ref, kCGPathFillStroke);
}
The drawing will be clipped to the update rectangle passed in.
Here are three possible solutions, depending on your constraints.
Set the class of the NSView object in the Xib file to be one of your custom classes. You mention that you know how to do custom drawing with a "hard-coded" NSView subclass, and this would work the same way. When the Xib file is loaded, the NSView is loaded into memory as an object of your custom class instead of the default. Changing the class type of the view in the Xib is simple with Interface Builder.
If the NSView object in the Xib file is already some kind of custom view that you do not want to override, you can add a subview to that view in Interface Builder, and simply make the subview into a custom class of your design (as in #1)
You can programmatically add a subview to the NSView object in the Xib file at runtime. In your sample code above, you could create an instance of your own custom NSView subclass, and then add it to the existing view using the addSubView: method.
How about doing all the drawing in a simple subview and add it to the XIB view (in viewDidLoad)?

Resources