I have serious performance issues in my game. I'm developing a jigsaw puzzle game. I cut down an image into smaller images, create a UIView for each jigsaw piece and assign each image into it's own UIView's CALayer.
Then a I create random UIBezier paths for the shapes as masks and assign them into CAShapeLayers and then I assign the shape layers to my UIView.layer.mask properties.
It all works like a charm up to the point when I try to move the jigsaw pieces. I detect touches and move the appropriate pieces' center property (CGPoint).
But it is sooo slow and "laggy" when I move a piece over other pieces.
I googled a lot, tried calling setNeedsDisplayInRect: my container view etc.
Also I found, that these methods redraw the whole thing and I dont need to redraw, I need only to move the pieces. And I've found that animating is a good approach, since it uses methods built up on OpenGL. So I tried animating (moving) the pieces using the UIView class'method in my jigsaw piece object (subclass of UIView):
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self.superview];
CGPoint offset = CGPointMake(touchLocation.x - _previousPoint.x, touchLocation.y - _previousPoint.y);
[UIView animateWithDuration:0
animations:^(void){
self.center = CGPointMake(self.center.x + offset.x, self.center.y + offset.y);
}
completion:^(BOOL finished){
}];
_previousPoint = touchLocation;
}
But it also didn't do the trick.
Does anyone have an idea how to improve the performance of this?
I still think that the "move only" approach would be the solution (not redrawing every frame, since redrawing is very expensive). But I don't know how to implement that.
Any ideas would be greatly appreciated, thanks in advance.
Related
When creating a new UIView or panning/swiping a view to a new position, whats the proper way of setting the new position and size. Currently, to move views resulting from a pan/swipe action, I'm using ratios based on the original position of the view that I'm moving and its relation to other views and items (navigation bar).
When moving a view due to a swipe I do the following:
CGFloat swipeUpDelta = -1 *(self.view.bounds.size.height - (self.view.bounds.size.height - recordView.frame.origin.y) - (self.navigationController.navigationBar.bounds.size.height + ([UIApplication sharedApplication].statusBarFrame.size.height)));
[UIView animateWithDuration:.1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
recordView.frame = CGRectOffset(recordView.frame, 0, swipeUpDelta);
} completion:nil];
Or creating a view that renders a sineWave across my viewcontroller:
self.sineWave = [[OSDrawWave alloc] initWithFrame:CGRectMake(-1*self.view.bounds.size.width, (.75*self.view.bounds.size.height), 2*self.view.bounds.size.width, .3125*(2*self.view.bounds.size.width))];
Everything works fine, but I wanted to know if there is a better way of doing these things.
I'm not sure I understand the question. initWithFrame: or myView.frame is considered the correct way of positioning a UIView. Using animateWithDuration: with a new frame size is also pretty standard.
I'm unsure of what you mean as well. When you move a UIView you can move the view a variety of ways, it depends on your code and what you want out of it. The below is a valid example of code to move a CALayer inside of a UIView to follow touch positions. You can use this, you can animate your view, you can use explicit animations when changing the position of the layer or center of the view (By default iOS animates any changed property values of a CALayer... you can ride off of this and enable this feature for UIViews if you please). If you're moving a UIView, it's a good idea to move the view according to the center property. This is pretty much how SpriteKit works and any high functioning animation engine (avoid always resetting the frame... especially if you don't need to).
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self];
boxLayer.position = p;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self];
[CATransaction begin];
[CATransaction setDisableActions:YES];
boxLayer.position = p;
[CATransaction commit];
}
As a note, note the comment in the following line of code
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
/*UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self]; // this causes the animations to appear sluggish as there is a consistant change in destination location. The above code corrects this.
[boxLayer setPosition:p];*/
This is valid, and may provide the results you want... it all depends on what you want.
How would I create a program so a dot starts in the center, and when I click the screen the dot follows where I clicked? Not as in teleports to it, I mean like changes it's coordinates towards it slightly every click. I get how I could do it in theory, as in like
if (mouseIsClicked) {
[mouseX moveX];
[mouseY moveY];
}
And make the class that mouseX and mouseY are have some methods to move closer to where the mouse is, but I just don't know any specifics to actually make it happen. Heck, I don't even know how to generate a dot in the first place! None of those guides are helping at all. I really want to learn this language though. I've been sitting at my mac messing around trying to get anything to work, but nothing's working anywhere near how I want it to.
Thanks for helping a total newbie like me.
If you are going to subclass UIView, you can use the touchesBegan/touchesMoved/touchesEnded methods to accomplish this. Something like:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
//slightly update location of your object with p.x and p.y cgpoints
[self setNeedsDisplay];
}
-(void)drawRect{
//draw your object with updated coordinates
}
You can create a dot and move it around based on taps all within your UIViewController subclass.
Make your dot by creating a UIView configured to draw the way you want - look into CALayer and setting dotview.layer.cornerRadius to make it be round (alternately you can make a UIView subclass that overrides drawRect: to make the right CoreGraphics calls to draw what you want). Set dotview.center to position it.
Create a UITapGestureRecognizer with an action method in your view controller that updates dotview.center as desired. If you want it animated, simply set the property within a view animation call & animation block like this:
[UIView animateWithDuration:0.3 animations:^{
dotview.center = newposition;
}];
You can download the sample code here that will show you general iOS gestures. In particular it has a sample that shows how to drag and drop UIViews or how to swipe them around. The sample code includes both driven and fire-n-forget animations. Check it out, its commented and I'd be happy to answer any specific questions you have after reviewing the code.
Generating a simple circle
// Above Implementation
#import <QuartzCore/QuartzCore.h>
// In viewDidLoad or somewhere
UIView *circleView = [[UIView alloc] initWithFrame:CGRectMake(32.0, 32.0, 64.0, 64.0)];
[circleView setBackgroundColor:[UIColor redColor]];
[circleView.layer setCornerRadius:32.0];
self.view addSubview:circleView];
I am creating a UIButton that follows the UITouch point, I want that when I Keep pressing the touch and the animation boundaries reach my touch point I perform some activity there.
Following is the code.
I am always getting x,y 0 for the UIButton.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[UIView beginAnimations:#"MoveAndStrech" context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationBeginsFromCurrentState:YES];
if([touches count] == 1) {
UITouch *touch = [touches anyObject];
self.uiButton.center = [touch locationInView:self.view];
}
[UIView commitAnimations];
UITouch *touchTemp = [touches anyObject];
CGPoint pCurrent = [touchTemp locationInView:self.view];
CGRect currentRect = self.uiButton.layer.bounds;
NSLog(#"Current Rect x:%f,Current y:%f",
currentRect.origin.x,
currentRect.origin.y);
NSLog(#"TouchPoint x:%f,TouchPoint y:%f",
pCurrent.x,
pCurrent.y);
if(CGRectContainsPoint(currentRect, pCurrent)) {
NSLog(#"Touched....in rect");
}
}
Regards
Sara
Replace
CGRect currentRect = self.uiButton.layer.bounds;
with
CGRect currentRect = self.uiButton.frame;
If that doesn't work, then
CGRect currentRect = self.uiButton.presentationLayer.frame;
Also, why aren't you using UIView animation blocks? It'd make your life much easier and your code much cleaner.
Several things. At first I thought you were missing the call to commitAnimations.
DO NOT PUT MULTIPLE STATEMENTS ON THE SAME LINE!!!! That is a very nasty coding habit that makes your code all but impossible to read.
Next, the old style beginAnimations/commitAnimations calls are outdated, and should not be used in iOS 4 or later. To quote the docs of beginAnimations:context:
Use of this method is discouraged in iOS 4.0 and later. You should use
the block-based animation methods to specify your animations instead.
You should be using the new block-based animation calls (of the form (animateWithDuration:animations:)
Next, UIView animations cause the object being animated to move to it's final location instantly. They only LOOK LIKE they are moving to their final location over time. They also disable user interaction during animations by default.
If you want to figure out where the view appears on the screen at any given moment then you need to check the view's layer's animationLayer. I have a sample project on Github (link) that includes a UIView animation of an image where you can tap on the image and the animation stops. This sample project shows how to use the view's layer's animation layer to figure out where the animation is located at the instant the user taps the screen.
If you want code that does something when the animated view gets to a certain point it would be more tricky. You might need to attach a CADisplayLink to your view (which will be called on each screen refresh) and use that to check the position of your animation and compare it to the current touch location.
I am making a math related activity in which the user can draw with their fingers for scratch work as they try to solve the math question. However, I notice that when I move my finger quickly, the line lags behind my finger somewhat noticeably. I was wondering if there was some area I had overlooked for performance or if touchesMoved simply just doesn't come enough (it is perfectly smooth and wonderful if you don't move fast). I am using UIBezierPath. First I create it in my init method like this:
myPath=[[UIBezierPath alloc]init];
myPath.lineCapStyle=kCGLineCapSquare;
myPath.lineJoinStyle = kCGLineJoinBevel;
myPath.lineWidth=5;
myPath.flatness = 0.4;
Then in drawRect:
- (void)drawRect:(CGRect)rect
{
[brushPattern setStroke];
if(baseImageView.image)
{
CGContextRef c = UIGraphicsGetCurrentContext();
[baseImageView.layer renderInContext:c];
}
CGBlendMode blendMode = self.erase ? kCGBlendModeClear : kCGBlendModeNormal;
[myPath strokeWithBlendMode:blendMode alpha:1.0];
}
baseImageView is what I use to save the result so that I don't have to draw many paths (gets really slow after a while). Here is my touch logic:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath moveToPoint:[mytouch locationInView:self]];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0f);
CGContextRef c = UIGraphicsGetCurrentContext();
[self.layer renderInContext:c];
baseImageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[myPath removeAllPoints];
[self setNeedsDisplay];
}
This project is going to be released as an enterprise app, so it will only be installed on iPad 2. Target iOS is 5.0. Any suggestions about how I can squeeze a little more speed out of this would be appreciated.
Of course you should start by running it under Instruments and look for your hotspots. Then you need to to make changes and re-evaluate to see their impact. Otherwise you're just guessing. That said, some notes from experience:
Adding lots of elements to a path can get very expensive. I would not be surprised if your addLineToPoint: turns out to be a hotspot. It has been for me.
Rather than backing your system with a UIImageView, I would probably render into a CGLayer. CGLayers are optimized for rendering into a specific context.
Why accumulate the path at all rather than just rendering it into the layer at each step? That way your path would never be more than two elements (move, addLine). Typically the two-stage approach is used so you can handle undo or the like.
Make sure that you're turning off any UIBezierPath features you don't want. In particular, look at the section "Accessing Draw Properties" in the docs. You may consider switching to CGMutablePath rather than UIBezierPath. It's not actually faster when configured the same, but it's default settings turn more things off, so by default it's faster. (You're already setting most of these; you'll want to experiment a little in Instruments to see what impact they make.)
http://mobile.tutsplus.com/tutorials/iphone/ios-sdk_freehand-drawing/
This link exactly shows how to make a curve smoother . This tutorial shows it step by step. And simply tells us how we can add some intermediate points (in touchesMoved method) to our curves to make them smoother.
For an ios app I'm writing, I'd like to take an photo from the photo library and then let the user "clean it up", essentially deleting parts of it that are not needed. For example, suppose the user chooses a photo of a person, my app only needs the head and everything else should be deleted so the user needs to clean the photo up by deleting the background, the body or other persons in the photo. Imagine a photoshop like experience but with only one tool - the eraser.
I'm looking for open source libraries, or examples or just tips of how to get started with that.
I know how to use a UIImagePickerController to select an image so the missing part is the actual editing. As a complete noob I'd be happy to get some advice on what would be a reasonable approach to this, preferably with some sample code or even a reusable library.
I suppose, in a high level, what I want to do is start with a rectangular image and make sure it has an alpha layer and then as the user touches parts of the image to delete them, I need to "delete" more pixels from the image by changing their alpha level to 0. But that's a too high level description which I'm not even sure is correct... Another reasonable requirement is undo support.
Another approach that comes to mind is using the original image and a mask image which the user edits while touching the screen and when "done", somehow compile the two images to one image with alpha. Of course, this is an implementation detail and the user need not know that there are two images on the screen.
If possible, I'd like to stay at the UIImage or UIImageView or Core Graphics levels and not have to mess with OpenGL ES. My gut feeling is that the higher graphics levels should be performant enough and easy to understand, maintainable clean code is a consideration...
Any advice is appreciated, thanks!
This turned out to be pretty easy, thanks for #Rog's pointers.
I'll paste my solution below. This goes in the controller code:
#pragma mark - touches
- (void) clipImageCircle:(CGPoint)point radius:(CGFloat)radius {
UIBezierPath* uiBezierPath = [UIBezierPath bezierPathWithArcCenter:point radius:radius startAngle:0 endAngle:2 * M_PI clockwise:NO];
CGPathRef erasePath = uiBezierPath.CGPath;
UIImage *img = imageView.image;
CGSize s = img.size;
UIGraphicsBeginImageContext(s);
CGContextRef g = UIGraphicsGetCurrentContext();
CGContextAddPath(g, erasePath);
CGContextAddRect(g,CGRectMake(0, 0, s.width, s.height));
CGContextEOClip(g);
[img drawAtPoint:CGPointZero];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
- (void) receiveTouch:(CGPoint)point {
NSLog(#"%#", NSStringFromCGPoint(point));
[self clipImageCircle:point radius:20];
}
- (void) endTouch {
NSLog(#"END TOUCH");
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// We currently support only single touch events
UITouch* touch = [touches anyObject];
CGPoint point = [touch locationInView:imageView];
if ([imageView hitTest:point withEvent:event]) {
[self receiveTouch:point];
}
}
- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self endTouch];
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self endTouch];
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
CGPoint point = [touch locationInView:imageView];
if ([imageView hitTest:point withEvent:event]) {
[self receiveTouch:point];
}
}
You will need to get well acquainted with Quartz 2D / CoreGraphics. This guide is a good start for you http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/Introduction/Introduction.html
The task you have described can be as simple or as complicated as you want. From letting the user use their fingers to erase the area around the photo by dragging their finger around (easy) to you trying to detect high contrast areas that help you guess where to cut (pretty complicated).
If you choose the former, you will essentially want to create a clipping mask based on user touches so have a look at the touchesBegan, touchesMoved and touchesEnded methods of UIView.
For the clipping mask, this is probably a good simple example to get you started How erase part of UIImage
Good luck with it, it sounds like a fun (if not challenging) project.