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)?
Related
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
I have some code in separate class, who bounded with UIView.
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect drawRect = CGRectMake(20, 262, 139, 169);
[self setFrame:drawRect];
CGContextSetLineWidth(context, 2);
CGContextSetStrokeColorWithColor(context, [[UIColor colorWithRed:125.0f/255.0f green:251.0f/255.0f blue:181.0f/255.0f alpha:1] CGColor]);
CGContextSetRGBFillColor(context, 200.0f/255.0f, 100.0f/255.0f, 100.0f/255.0f, 1.0f);
CGContextFillRect(context, drawRect);
But after clicking this view, size of UIView resizes to dimensions from interface builder model from my storyboard.
What does matter, and what's wrong with it?
drawRect: is not the place to change the frame. If the system can't automatically handle resizing the frame (by scaling the view, for instance), then drawRect: is called in response to resizing the frame. If you could change the frame size in the middle of drawing, you'd have an infinite loop.
If you want the view to be in the rectangle (20,262,139,169), then that's simple to put in autolayout. Just put the view where you want it, and create constraints to keep it there using Interface Builder.
If you don't want autolayout (not sure why you'd want to turn it off here, but if you did), you can turn it off and call setFrame: in viewDidLoad. But there's no reason to call it again in drawRect:.
I am currently working on an app that draws a series of rectangles (CGRect) on a view and sizes them based on the size of the UIView. This code is contain in a class file that inherits UIView (rectangle.h/rectangle.m). I cannot for some reason use self.view.bounds.height or self.view.bounds.width to get the values I need. I am using the overridden method "drawrect" that is built into the class. How can I get the view size? or if thats not possible, how can I implement this?
Use
self.view.bounds.size.width
self.view.bounds.size.height
or
if self class is itself a UIView
self.bounds.size.width
self.bounds.size.height
You can get the view size using the following :
CGSize viewSize = self.view.frame.size;
NSLog(#"View Size : %#",NSStringFromCGSize(self.view.frame.size));
I have a ViewController on storyboard. I have used the interface builder to set a toolbar at the bottom of the screen. I have set the custom view to a view overrides drawRect. However, for the life of me, I cannot get anything ever to show up on screen called from that drawRect. drawRect itself is called just fine, but nothing shows up on screen.
Also, I have this ViewController with a method that uses AVCaptureSession to toggle its background to a live view from camera input. I had suspected that this might have been the cause for error, but after removing all references of AVCaptureSession, I still cannot get this to work.
Sorry for my writing and/or lack of logic, I don't have any sleep right now.
Edit: Here is a small example of code that won't work. Every method inside gets called, but nothing is to show.
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);
CGContextMoveToPoint(context, 0,0); //start at this point
CGContextAddLineToPoint(context, 100, 100); //draw to this point
// and now draw the Path!
CGContextStrokePath(context);
}
The documentation for UIView says to not call [super drawRect] if you're overriding UIView. Remove that call! It's been known to cause strange behaviour.
According to the docs:
If you subclass UIView directly, your implementation of this method
does not need to call super. However, if you are subclassing a
different view class, you should call super at some point in your
implementation.
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?