Cocos2d: Drawing on a UIImageView causes weird line jumps - opengl-es

I am implementing a CCLayer subclass that houses 2 UIImageViews. The views and layer are all the same size: I initialized the UIImageViews with the same frame, and set the contentSize of the layer to be the frame as well. Everything is working fine, but it seems as though something is going haywire with the first point when drawing. When the image is just tapped there is no line jump, but when I attempt to draw a stroke, as soon as I move my finger, the start of the line jumps down randomly(so in this screenshot I am drawing from left to right except for bottom-left line). I am not sure where I am going wrong in my code:
//_drawImageView is the UIImageView I am drawing in.
#pragma mark - Touch Methods
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
penStroked = NO;
_prevPoint = [touch locationInView:[[CCDirector sharedDirector]view]];
CGRect rect = self.boundingBox;
if (CGRectContainsPoint(rect, _prevPoint)) {
return YES;
}
return NO;
}
- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
penStroked = YES;
_currPoint = [touch locationInView:_drawImageView];
UIGraphicsBeginImageContext(self.contentSize);
[_drawImageView.image drawInRect:CGRectMake(0, 0, _drawImageView.frame.size.width, _drawImageView.frame.size.height)];
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), _prevPoint.x, _prevPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), _currPoint.x, _currPoint.y);
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), penSize);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), penColor.r, penColor.g, penColor.b, 1.0);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeNormal);
CGContextStrokePath(UIGraphicsGetCurrentContext());
_drawImageView.image = UIGraphicsGetImageFromCurrentImageContext();
[_drawImageView setAlpha: penOpacity];
UIGraphicsEndImageContext();
_prevPoint = _currPoint;
}
I was having a really hard time initially aligning the point at which the line would be draw n and the actual position of my finger, so that is why in the ccTouchBegan method the touch is taken from the CCDirector and in ccTouchMoved it is taken from the _drawImageView. This is the only way it seems to draw perfectly besides the initial wonky behavior.

The problem was definitely caused by the differing references for the touches. As I had said before though, it was the only way to have the line match the position my finger was in. I figured that the line jump was due to the line of code:
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), _prevPoint.x, _prevPoint.y);
The problem was that on the first go around, or the first time that the pen/finger is stroked, the previous point was referenced from the CCDirectors view while the current point was from the imageView. I created an int variable, that was incremented every time the pen was stroked and when penStrokedInt == 1 (first stroke), I corrected the _prevPoint to the WorldSpace using the current point coordinates:
_prevPoint = [self convertToWorldSpace:_currPoint];
which basically got rid of the very first point in the line. It's not the best solution, but now it works! Make sure to reset the penStrokedInt back to 0 when the ccTouchEnded.

Related

El Capitan broke my NSView animations

I have a Mac app that uses a NSAnimationContext animation grouping to animate one NSView offscreen and another NSView onscreen. Prior to beginning the animation grouping I position the offscreen NSView in the position that I want it to originate from when it animates onscreen.
Under Yosemite and earlier versions this worked perfectly but under El Capitan it is as if the NSView never gets positioned in the start position that I specify so it animates onscreen from the wrong direction.
//Position offscreen view at correct starting point.
offscreenView.frame = STARTING_OFFSCREEN_RECT;
//Create animation grouping
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:animationDuration];
[[NSAnimationContext currentContext] setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[[NSAnimationContext currentContext] setCompletionHandler:^{
/*
Do cleanup stuff here
*/
}];
//Move the views
onscreenView.frame = ENDING_OFFSCREEN_RECT:
offscreenView.frame = ENDING_ONSCREEN_RECT;
//End Grouping
[NSAnimationContext endGrouping];
I've debugged this to the best of my ability and it appears to me that the setting of offscreenView's frame at the very beginning is not actually occurring.
Does anybody know what I'm doing wrong?
I had very similar problem - offscreenView sometimes starts from the wrong position.
The offscreenView.layer appears to be messed up.
I fixed it by adding the following to my clean-up code:
onscreenView.layer = nil;
so that the next time the offscreenView is animated it will start with clean layer.
Or maybe in your case reset the layer before starting the animation:
offscreenView.layer = nil;
//Position offscreen view at correct starting point.
offscreenView.frame = STARTING_OFFSCREEN_RECT;
//Create animation grouping
...
NOTE:
In my animation I add the offscreenView every time to the superView:
//Position offscreen view at correct starting point.
offscreenView.frame = STARTING_OFFSCREEN_RECT;
[superView addSubview:offscreenView];
//Create animation grouping
...
And in the clean-up code I remove the onscreenView as well:
[onscreenView removeFromSuperview];
onscreenView.layer = nil;

xcode touchesBegan multiple tasks

I am using touchesBegan and touchesMoved to move a UIImageView to wherever you touch on the screen. It moves a marker (the UIimageView I moved), draws a shape in the UIImageview and reads off a graph.
However whenever I try to do anything with that data eg update a label, the UIImageView scurries back to the place it started instead of staying where it is.
-(void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event{
UITouch *myTouch =[[event allTouches] anyObject];
CGPoint location = [myTouch locationInView:self.view];
int x = location.x;
int y = location.y;
NSLog(#"x = %i",x);
NSLog(#"y = %i",y);
CGPoint p={x, y};
circles.center = p;
The code above works fine and moves the image to its new location. However, should I add somethig really simple like
changeInY.text = [NSString stringWithFormat:#"Main Label"];
and the UIImageView (circles) is no longer at .center = p. The label updated and the UIImageView rushed back to the place I dumped it on the storyboard before any touches were made. It definitely moves, but rushes back. Without the label instruction it stays where it is and completes the next touch.
It will give my x-position and y-postition and let me print them in an NSLog statement, but it won't let me update a label with their values, or do anything that isn't to do with that UIImageView.
I don't want to dynamically create a UIImageView at runtime. I want to use that one and move it.
I'm fairly sure its the circles.center = p line that needs changing, but I don't know how to tell it to make that change and stay there.
Any help would be very welcome
Turns out after about 14 hours of rewriting the code in every conceivable manner, that I just needed to place the offending label on top of a 'view' box. I had some kind of layering issue. Once the label was placed on an invisible 'View' almost every combination of code I have tried in the last two days worked.

Resizing an NSView mid-animation

I have a normal NSView that is resizable by dragging the window edges.
If the view is resized during an [NSView animator] animation, it continues to animate to the final size of the original animation, but does not take into account the new window size.
Here is a simple example project. Double click to begin the animation, then resize the window before it finishes.
What is the best way to make the animation take account of the new frame size?
IMHO, the best way would be to stop the animation as soon as the resizing phase begins.
During the resize phase, the user is in control and sets the size of the window manually.
When the resizing phase ends, the window is already set to the desired size, so there is non need to do more.
This kind of problem is best solved with an NSTimer instead of the animator function:
Let the timer call a function repeatedly, until the animation is "complete".
Once complete, end the timer (invalidate).
The function to be called repeatedly in each loop grabs the actual framesize of the window and the actual framesize of your view and simply adds the third of the difference of the two to the frame of the view, like:
frame.size.height += diffHeight/3.0;
So, no matter what happens, the view grows or shrinks closer and closer to its destination.
Once the abs(of the difference) is less then e.g. 0.2 you set the view directly to the desired size and end the timer.
This is direct, uses only little code and you need not listen to any events while it performs pretty well. :-)
Here are the critical codes to initiallize the animation (timer must be an instance of your class):
if(timer)return;
timer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(resizeView:) userInfo:[NSNumber numberWithBool:status] repeats:YES];
[timer setTolerance:0.02];
I use the word status instead of your word closed, the function to be repeatedly called might then look somewhat like:
- (void)resizeView:(id)userInfo;
{
BOOL status = [(NSNumber *)[userInfo userInfo] boolValue];
double startwid,stopwid;
NSRect newSizeRect = [[self window] frame];
stopwid = newSizeRect.size.width;
if(status){
stopwid -= 100.0;
}
NSRect cbgRect = [self frame];
startwid = cbgRect.size.width;
double diff = stopwid-startwid;
if(fabs(diff)<0.2){
diff = 0;
startwid = stopwid;
[timer invalidate];
timer = nil;
//NSLog(#"stop");
}
//NSLog(#"%f - %f = %f /10 = %f",stopwid,startwid,diff,diff/3.0);
cbgRect.size.width = startwid+diff/3.0;
[self setFrame:cbgRect];
}

Drawing into transparent overlay subview NSView

I've got a relatively simple Cocoa on Mac OS X 10.6 question to ask. I have a main NSView (ScreensaverView, actually) that is not layer backed and is otherwise unremarkable. It does some basic drawing in its drawRect via NSRectFill and NSBezierPath:stroke calls (dots and lines, basically).
I've also got a NSView-derived subclass that is acting as a child subview. I'm doing this with the goal to draw simple lines in the subview that draw on top of whatever is drawn in the main view, but then can be somehow "erased" revealing whatever the lines obscured. The code for this subview is quite simple:
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)drawRect:(NSRect)dirtyRect {
// Transparent background
[[NSColor clearColor] set];
NSRectFillUsingOperation(dirtyRect, NSCompositeCopy);
// If needed for this update, draw line
if (drawLine) {
// OMITTED: Code that sets opaque NSColor and draws a line using NSBezierPath:stroke
}
// If needed for this update, "erase" line
if (eraseLine) {
[[NSColor blackColor] set]; // clearColor?
// OMITTED: Code that draws the same line using NSBezierPath:stroke
}
}
With the code as shown above, any time the subview draws, the main view goes black and you only see the subview line. When the subview isn't being updated, the main view contents appear again.
Things I've tried, with varying results:
I tried experimenting with making the subview return YES from an overridden isOpaque (which I realize isn't really correct). When I do this, both views draw properly during a subview update, however the subview line overwrites anything it is drawn on (ok) and then when erased, also leaves a large black line where it was (what I was trying to avoid). Trying to "erase" the line using clearColor instead of blackColor results in the line remaining on screen.
I tried making both (and/or just the subview) layer-backed views via calling [self setWantsLayer:YES] in init, and this results in a completely black screen.
I feel like I'm missing something really basic, but for whatever reason, I can't seem to figure it out. Any and all suggestions are greatly appreciated.
I think you are fundamentally misunderstanding how view drawing works. Basically, every time drawRect: is called on your view, the drawing commands in drawRect: are executed. This might happen automatically or when you issue the view a setNeedsDisplay: message.
Normally, when drawRect: is called on the view, the view is erased and drawing begins anew. Nothing remains in the view from the previous call to drawRect:. You do not need to fill the view with [NSColor clearColor] in order for it to be transparent.
Note that this is only the case if your view returns NO from isOpaque, which is the default. If your view is returning YES from isOpaque then you will need to ensure you erase the view before drawing, however in your particular case you should not return YES from isOpaque because you want your view to be transparent.
You do not need to "erase" the line you've drawn. It will be erased for you. Instead, you need to simply not draw it when you don't want it drawn.
Basically, your view should store some flag (such as your BOOL ivar named drawLine or something similar). Then, in your drawing code you should simply check if this value is set. If it is, you should draw the line. If not, then just do nothing. Your code should be reduced to this:
- (void)drawRect:(NSRect)rect
{
// If needed for this update, draw line
if (drawLine) {
// OMITTED: Code that sets opaque NSColor and draws a line using NSBezierPath:stroke
}
}
If you want to change the state and redraw the view, all you need to do is change the value of the drawLine ivar and ask the view to redraw using [yourView setNeedsDisplay:YES].
You can easily do this with a timer:
- (void)awakeFromNib
{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(update:) userInfo:nil repeats:YES];
}
- (void)update:(NSTimer*)timer
{
drawLine = !drawLine;
[self setNeedsDisplay:YES];
}

NSScrollView scroll bars are of the wrong length

I have a Cocoa window, whose content view contains an NSScrollView that, in turns, contains a fixed-size NSView.
Upon launching the program, the scroll bars displayed initially are too small, as if the content size was much larger than it actually is:
When I start playing with, e.g., the vertical scroll bar, and bring it back to the original position at the top, it gets resized to its expected size (which corresponds to the ratio of scroll view and content view sizes):
(Notice the horizontal bar, which still has incorrect size. If I then play with it, and bring it back to its leftmost position, it gets resized to the correct size.)
I also encountered the same problem, I have searched everywhere but it seems no one else experiences this problem. Fortunately I found a hack which solves the problem.
What I did notice was that when the window is resized or maximized the scrollbars resize to the expected size (autoresizing has to be enabled). This is because when the window resizes so does the scrollview and the length of the scroll bars gets recalculated and is calculated correctly. Possibly due to some bug the scroll bar lengths are not calculated correctly on initialization. Anyway to fix the problem, in your application delegate create an outlet to your window. Override the "applicationDidFinishLaunching" method and inside it call the method "frame" on the window outlet, which returns the current NSRect of the window. Using the returned value add one to the size.width and size.height. The call the method setFrame with display set to YES. This will resize the window and force the size of the scrollbars to be recalculated.
Here is the code for applicationDidFinishLaunching Below
(void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Get the current rect
NSRect windowRect = [_window frame];`
// add one to the width and height to resize window
windowRect.size.width += 1;
windowRect.size.height += 1;
// resize window with display:YES to redraw window subviews
[_window setFrame:windowSize display:YES];
}
I encountered this issue when modifying an NSTextView textContainer size to toggle line wrapping. Resizing the enclosing view does cause the correct scroll view height to be used, however its a brutal solution.
NSScrollView supports -reflectScrolledClipView. Calling this directly in my case had no effect except when delayed on the runloop:
[textScrollView performSelector:#selector(reflectScrolledClipView:) withObject:textScrollView.contentView afterDelay:0];
The scroller position is correct but there is a scroller redraw. So it looks as if part of the view geometry is calculated when drawing. A better solution is therefore:
NSDisableScreenUpdates();
[textScrollView display];
[textScrollView reflectScrolledClipView:textScrollView.contentView];
[textScrollView display];
NSEnableScreenUpdates();
Building on the answer from jstuxx above, if you don't want the window to visibly resize, try:
NSRect windowRect = [[[self view] window] frame];
windowRect.size.width += 1;
windowRect.size.height += 1;
[[[self view] window] setFrame:windowRect display:YES];
windowRect.size.width -= 1;
windowRect.size.height -= 1;
[[[self view] window] setFrame:windowRect display:YES];
I had to put this code after where I was programmatically adding the scroll view to my interface.

Resources