How to use a custom view correctly? - objective-c

I have been trying to make a simple drawing program. Recently, I have figured out to draw shapes in a custom view for this purpose. My problem is that I have to draw everything at a single point in time. I don't know if that actually makes sense, but it seems to me that it calls the drawRect method only once, at that "once" is on startup.
Here is my code so far:
Header file.
NSBezierPath *thePath;
NSColor *theColor;
NSTimer *updateTimer;
NSPoint *mousePoint;
int x = 0;
int y = 0;
#interface test : NSView {
IBOutlet NSView *myView;
}
#property (readwrite) NSPoint mousePoint;
#end
Then, implementation in the .m file.
#implementation test
#synthesize mousePoint;
- (void) mouseDown:(NSEvent*)someEvent {
CGEventRef ourEvent = CGEventCreate(NULL);
mousePoint = CGEventGetLocation(ourEvent);
NSLog(#"Location: x= %f, y = %f", (float)mousePoint.x, (float)mousePoint.y);
thePath = [NSBezierPath bezierPathWithRect:NSMakeRect(mousePoint.x, mousePoint.y, 10, 10)];
theColor = [NSColor blackColor];
}
- (void) mouseDragged:(NSEvent *)someEvent {
mousePoint = [someEvent locationInWindow];
NSLog(#"Location: x= %f, y = %f", (float)mousePoint.x, (float)mousePoint.y);
x = mousePoint.x;
y = mousePoint.y;
[myView setNeedsDisplay:YES];
}
- (void) drawRect:(NSRect)rect; {
NSLog(#"oisudfghio");
thePath = [NSBezierPath bezierPathWithRect:NSMakeRect(x, y, 10, 10)];
theColor = [NSColor blackColor];
[theColor set];
[thePath fill];
}
#end
On startup, it draws a rectangle in the bottom left corner, like it should. The problem is, the drawRect method is only called on startup. It just won't fire no matter what I do.
EDIT: I have just updated the code. I hope it helps.
SECOND EDIT: I have really simplified the code. I hope this helps a bit more.

Short Answer:
When your view's state is changed such that it would draw differently, you need to invoke -[NSView setNeedsDisplay:]. That will cause your view's drawRect: method to be called in the near future. You should never call drawRect: yourself. That's a callback that's invoked on your behalf.
When events occur in your application that cause you to want to change your drawing, capture state about what happened into instance variables, invoke setNeedsDisplay: and then later when drawRect: is called do the new drawing.
Long Answer:
In Cocoa, window drawing is done with an pull/invalidation model. That means the window has an idea of whether or not it needs to draw, and when it thinks it needs to draw it draws once per event loop.
If you're not familiar with event loops you can read about them on Wikipedia
At the top level of the application you can imagine that Cocoa is doing this:
while (1) {
NSArray *events = [self waitForEvents];
[self doEvents:events];
}
Where events are things like the mouse moving, the keyboard being pressed, and timers going off.
NSView has a method -[NSView setNeedsDisplay:]. It takes a boolean parameter. When that method is invoked the window invalidates the drawing region for that view, and schedules an event for the future to do redrawing - but only if there isn't a preexisting redrawing event scheduled.
When the runloop spins next time, the views that were marked with setNeedsDisplay: are re-drawn. This means you can call setNeedsDisplay: several times in a row and drawing will be batched to one call of drawRect: in the future. This is important for performance reasons and means you can do things like change the frame of a view several times in one method but it will only be drawn once at the final location.

The code in your example has a couple of problems. The first is that all drawing code must be in the drawRect: method or a method called from drawRect:, so the drawing code you've placed in your other methods will have no effect at runtime. The second problem is that your code should never directly call drawRect:; instead, the framework will call it automatically (if necessary) once per event cycle.
Instead of hardcoding all the values, consider using instance variables for things you want to be able to change at runtime, for example, the drawing color and rectangle. Then in your mouseDragged: method, send the view (myView in your example) a setNeedsDisplay: message. If you pass YES as the argument, the drawRect: method will be called for you by the framework.

Related

Why NSView leaves an image on superview on where it was when I move it?

I am working on a small application on Mac that I need to create customed cursor and move it. I used NSImageView to implement it. However when I call setFrameOrigin (the same to setFrame) it will leaves images on the previous place.
Here is my code:
#property (nonatomic, strong) NSImageView *eraserView;
this is the define
_eraserView = [[NSImageView alloc] initWithFrame:CGRectMake(100, 100, 32, 32)];
_eraserView.image = [NSImage imageNamed:#"EraserCursor"];
[self.view addSubview:_eraserView];
[_eraserView setHidden:YES];
here is the initialization. Everything goes well until now but:
- (void)setImageatPoint:(NSPoint)point
{
[_eraserView setFrameOrigin:point];
}
- (void)hidePenImage
{
[_eraserView setHidden:YES];
}
- (void)unhidePenImage: (BOOL)isEraser
{
[_eraserView setHidden:NO];
}
These are methods I use to change the state of the NSImageView. They will be called by another class using delegate when corresponding events of trackpad occurs.
However every time I change the state of the NSImageView, it seems like it is drawn on the superview.
I debugged it and found there was no extra subviews. And when I use setHidden it has no effect on those tracks. I think it somehow did something to the CALayer, but I have no idea how to fix it.
Screenshots would help but in general if you move a view or change the area of the view that is drawn, you need to redraw.
To do this it kind of depends on how your drawing happens. Calling setNeedsDisplay may not be enough if your implementation of drawRect only draws a sub rect of the view bounds. Cocoa only draws what it is told to draw.
You can erase sections of the view that should be empty by drawing (filling) where it should be empty. That means drawing a color ( NSColor clearColor if nothing else) in the area that was previously drawn.

How to change background color for NSview in Cocoa

I want to change background color for many nsview. I override drawRect: on subclass NSview but i don't know how to set background color for myview( is reference IBOUTLET). please help me. Thanks so much
Code for CustomView.h
#import <Cocoa/Cocoa.h>
#interface CustomView : NSView
#end
Code for CustomView.m
#import "CustomView.h"
#implementation CustomView
- (void) drawRect:(NSRect)dirtyRect {
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
#end
And in main class, i added #import "CustomView.h" but i don't know how to set background for myview.
Welcome to Cocoa drawing.
Cocoa drawing uses Quartz which is a PDF model.
Drawing in this occurs in a back to front procedural order.
In Quartz drawing there is a drawing environment state object called the Graphics Context.
This is an implicit object in many of the drawing ops in AppKit.
(in Core Graphics or other APIs it could need to be explicitly called)
You tell the Graphics Context what the current color and other parameters are, then draw something, then change parameters and draw more, etc...
In AppKit, you do this by sending a message to the NSColor object, which is weird. but that's how it works.
In your drawRect: method you should call super first usually, because you probably want your drawing on top of that...
- (void) drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// This next line sets the the current fill color parameter of the Graphics Context
[[NSColor whiteColor] setFill];
// This next function fills a rect the same as dirtyRect with the current fill color of the Graphics Context.
NSRectFill(dirtyRect);
// You might want to use _bounds or self.bounds if you want to be sure to fill the entire bounds rect of the view.
}
If you want to change the color, you'll need an #property NSColor
You might need more than one for your drawing.
That allows you to set the color.
You might want the view to use KVO and observe its own color property then draw itself if the color property changes.
You could do a lot of different things to set the color. (a button or pallette elsewhere) But all of them would eventually result in sending a message to set the color of a property of your view for drawing.
Finally, if you want to update the drawing, you need to call [myView setNeedsDisplay:YES]; where myView is a reference to an instance of the NSView subclass.
There is also display but that's forceful.
setNeedsDisplay: says to schedule it on the next run of the event loop (runLoop). display kind of makes everything jump to that right away.
The event loop comes back around fast enough you shouldn't force it.
Of note, setNeedsDisplay: is the entire view.
In a fancy ideal world with complex views, you might want to more appropriately optimize things by calling setNeedsDisplayInRect: where you designate a specific CG/NSRect of the view as needing to be redrawn.
This allows the system to focus redrawing to the smallest union rect possible in the window.
I'm super late, but this is how I do it - there's no need to sub class:
NSView *myview = [NSView new];
[view setWantsLayer:YES];
view.layer.backgroundColor = [NSColor greenColor].CGColor;

Putting a fade in fadeout Effect on objective C

I want this to be a background in my app for Ipad.
I'm building everything in objective C (native app)
I need a little help figuring out how to animate each of the triangles over the image (overlay) so it fades in and out independently of each other, the goal to make a constant shimmer like effect so the image doesn't feel sos tatic. Do i have to animate each triangle independently? Is there any algorithm that i should be looking at so it seems kinda random but isn't.
Here is the background image
I need some guidance on where to start and how to approach this problem, and feedback would be appreciated.
a) My advice is to use UIViewAnimationWithBlocks introduced in iOS 4. If you have a solid grasp on blocks, they can be very useful. Here's an example I created in as little as 5 minutes to illustrate:
typedef void(^FadeInOutBlock)(void);
#interface PMViewController ()
#property (nonatomic, copy) FadeInOutBlock fadeInOutBlock;
#end
Here we declare a typedef to save us from doing the block syntax all over again. We also create a property to hold the animation block.
#implementation PMViewController
#synthesize myView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
__block PMViewController *_self = self;
self.fadeInOutBlock = ^{
[UIView animateWithDuration:0.5f animations:^{
// fade out effect
_self.myView.alpha = 0.0f;
} completion:^(BOOL success){
[UIView animateWithDuration:0.5f animations:^{
// fade in effect
_self.myView.alpha = 1.0f;
} completion:^(BOOL success){
// recursively fire a new animation
if (_self.fadeInOutBlock)
_self.fadeInOutBlock();
}];
}];
};
}
We create the animation, within an animation. You start off with the fade out, where myView's alpha will be reduced to 0.0f in 0.5f seconds. After it's completed, a second animation will be fired, restoring the alpha for myView back to 1.0f and finally, firing out the first animation, again. (Animception)
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.fadeInOutBlock)
self.fadeInOutBlock();
}
#end
And finally, in view did appear you fire it off for the first time.
b)
Now, for the shimmering animation that you mention, I suggest you separate each triangle into it's own UIView and use the technique above, using different durations and alphas.
If you have to many small UIViews, group them up into a bigger UIView (by using the addSubview method) and apply the animation to those 'container' UIViews.
For instance, you could have four separate UIView containers, that have a bunch of separate UIViews as their children. You could then create four block animations, one for each container, and then apply the animation to them. I bet experimenting with that, you would be able to create pretty good effects.
You can do this without any special librarys. So you need to create a black UIView with an alpha of 0. Then create an NSTimer that increases the alpha of the UIView.

Draw waveform in NSView

I need to draw a waveform in an NSView. (I have all the samples in an array). The drawing must be efficient and really fast without clipping, flickering, etc, it must be smooth. The waveform will be "moving" according to the song position and some changes to the samples (DSP processing) will be shown as visual representation onto NSView in realtime.
I'm familiar drawing lines, arcs, etc onto canvas objects and I have developed apps doing such things but not on Mac OS X ...
I want to ask if anyone can guide me where to start drawing! Core Animation, OpenGL, simple override drawing methods, ??, etc. Which would be the best practice - API to use?
I would keep it simple and create an NSView subclass with an audioData property that uses Cocoa drawing. You could call [view setAudioData:waveArray] which would in turn call [self setNeedsDisplay:YES].
In your drawRect: method you could then iterate through the samples and use NSRectFill() accordingly. Here sample's value is between 0 and 1.
- (void)drawRect:(NSRect)dirtyRect {
[[NSColor blueColor]set];
for (id sample in self.waveArray) {
NSRect drawingRect = NSZeroRect;
drawingRect.origin.x = [self bounds].origin.x;
drawingRect.origin.y = [self bounds].origin.y + ([self.waveArray indexOfObject:sample]/([self.waveArray count] - 1.0));
drawingRect.size.width = [self bounds].size.width/[self.waveArray count];
drawingRect.size.height = [self bounds].size.height * [sample value];
NSRectFill(drawingRect);
}
}
This code isn't exact, and you should be sure to make it more efficent by only drawing samples inside dirtyRect.
I would start with a really long and thin image to represent a single bar/column for the waveform.
My plan would be to have a NSTimer that moves all bars of the wave one to the left every 0.01 seconds.
So something like this in the loop.
for (int x; x < [WaveArray count] ; x++)
{
UIImageView * Bar = [WaveArray ObjectAtIndex: x];
[Bar setCenter:CGPointMake(Bar.center.x-1,Bar.center.y)];
}
Now all you have to do is create the objects at the correct hight and add them to the WaveArray and they all will be moved to the left.

Drawing a large number of lines (CGContextBeginPath) on an iPad

I'm trying to make an ipad application that draws alot, but I really mean alot of lines on stage (10.000+)
using this simple forloop, my ipad crashes after 40~60 seconds (without showing result)
for ( int i = 0; i < 10000; i++ )
{
int r_x = rand() % 750;
int r_y = rand() % 1000;
CGPoint pointpoint = CGPointMake(r_x, r_y);
UIColor *st = [[GetColor alloc] getPixelColorAtLocation:pointpoint];
DrawLine *drawview = [[DrawLine alloc]initWithFrame:CGRectMake(r_x, r_y, 20, 20) selectedcolor:st];
[self.view addSubview:drawview];
[drawview release];
[DrawLine release];
[GetColor release];
}
and this is my "DrawLine" class:
- (id)initWithFrame:(CGRect)frame selectedcolor:colors{
if ((self = [super initWithFrame:frame])) {
selectedcolor_t = colors;
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)drawRect:(CGRect)frame{
CGContextRef c = UIGraphicsGetCurrentContext();
float* colors = CGColorGetComponents(selectedcolor_t.CGColor);
CGContextSetStrokeColor(c, colors);
CGContextBeginPath(c);
CGContextMoveToPoint(c, 0.0f, 0.0f);
CGContextAddLineToPoint(c, 20.0f, 20.0f);
CGContextStrokePath(c);
}
how can I solve this problem? How can I draw this much subviews without crashing the iOS?
thanks so much!! :)
Please reconsider what you are doing there:
In line 4 of your loop, you alloc an instance of GetColor — which you never use again. Ask yourself: Does that make any sense from a design point of view?
In that same line, if you don't violate Cocoa's naming-conventions, you create a UIColor that is never released...
Then in line 8 you release the class-object of DrawLine (ditto that for the next line and the GetColor-class). This is terribly, horribly wrong!
Please visit the Memory Management Programming Guide at the iOS Dev-Center and read the first two sections (again)!
Besides that, re-evaluate your design:
Should GetColor really be a class, so that you create instances? Wouldn't a simple helper-function for color interpolation make more sense in this context?
If it should be a class, why not create just one instance of it outside of the loop and simply query it repeatedly for the colors?
Do you really need a subclass of UIView to draw a single straight, solid, single-colored line? If the lines need not be updated, you should (as Richard and nacho4d suggested) draw all of them in one object (e.g. by a custom UIView or by a delegate of CALayer implementing the drawLayer:inContext: method). If you need to update those lines later, you could simply (ab)use CALayer...
In the latter case, your problem then becomes:
Calculate your random coordinates.
Calculate your color.
Create an opaque CALayer with
a) that color as its backgroundColor,
b) a width of 20 * sqrt(2),
c) a height of whatever-you-want-to-be-the-width-of-that-line,
d) your point as its origin and
e) a rotation of 45.
Add that layer as a sublayer to self.view's layer.
Cheers
Daniel
If your lines are static (not moving later, not animating, etc) , as they seem to be, you could also draw all the lines in a single drawRect: in one view without creating 1000 of CALayers.
I can't tell if this is faster than drawing 1000 CALayers (because CoreAnimation is hardware accelerated and CoreGraphics is not) but it's surely lighter since all the lines will be flattened in a single bitmap. (which is the context of your view)
Just move your for loop inside your drawRect: and follow danyowde advices.( you just need one color object or a helper function but not to create a color each iteration)
Good luck, Hope it helps;)