CALayer custom drawing glitch - ios

I'm working on glow effect CALayer. I've created an UIView category. And my custom drawing gives me this strange animation:
Please, check my full code.
UPDATE:
I've added some code, that I think crucial.
- (void)setGlowHidden:(BOOL)glowHidden {
self.layer.masksToBounds = glowHidden;
self.clipsToBounds = glowHidden;
if (self.glowLayer == nil) {
self.glowLayer = [GlowLayer layer];
self.glowLayer.frame = self.layer.bounds;
self.glowLayer.parentView = self;
self.glowLayer.masksToBounds = NO;
self.glowLayer.contentsGravity = kCAGravityCenter;
[self.layer addSublayer:self.glowLayer];
}
//...
[self.glowLayer setNeedsDisplay];
[self.glowLayer performSelector:#selector(setNeedsDisplay) withObject:nil afterDelay:0.0];
}
//...
- (void)drawInContext:(CGContextRef)ctx {
CGFloat delta = MAX(self.glowSize, self.innerGlowSize);
CGRect drawingRect = CGRectMake(-delta, -delta, CGRectGetWidth(self.parentView.frame) + 2 * delta,
CGRectGetHeight(self.parentView.frame) + 2 * delta);
CGPoint offsetPoint = CGPointMake(-drawingRect.origin.x, -drawingRect.origin.y);
self.frame = drawingRect;
[self setNeedsLayout];
[self layoutIfNeeded];
UIGraphicsBeginImageContextWithOptions(drawingRect.size, NO, 0.0f);
// [self.parentView drawViewHierarchyInRect:rect afterScreenUpdates:YES];
[self.parentView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *textImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIGraphicsPushContext(ctx);
CGContextSaveGState(ctx);
if (_glowSize > 0) {
CGContextSetShadow(ctx, CGSizeZero, _glowSize);
CGContextSetShadowWithColor(ctx, CGSizeZero, _glowSize, _glowColor.CGColor);
}
[textImage drawAtPoint:offsetPoint];
CGContextRestoreGState(ctx);
//...
}

Related

How to make uiimageview circle.?

I need to make UIImageView in circle radius, I am using this block of code for this taks.
-(void)viewWillAppear:(BOOL)animated {
[self performSelector:#selector(setStyleCircleForImage:) withObject:_imageview afterDelay:0];
[super viewWillAppear:YES];
}
-(void) setStyleCircleForImage:(UIImageView *)imgView {
imgView.layer.cornerRadius = _imageview.frame.size.height / 2.0;
imgView.clipsToBounds = YES;
}
Its working perfect in iOS 5 but when test in other device it shape is change I don't know why it happens. Please make some help.
Better to use mask to make circle like this function
- (void)viewDidLoad {
[super viewDidLoad];
[self makeCircleImage: _imageview];
}
-(void)makeCircleImage:(UIImageView *)img{
CGFloat img_width = img.bounds.size.width;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(img_width,img_width), NO, 0);
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(c, [UIColor blackColor].CGColor);
CGContextFillEllipseInRect(c, CGRectMake(0,0,img_width,img_width));
UIImage* maskim = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CALayer* mask = [CALayer new];
mask.frame = CGRectMake(0,0,img_width,img_width);
mask.contents = (id)maskim.CGImage;
img.layer.mask = mask;
}
Try This
yourImageView.layer.cornerRadius = yourImageView.frame.size.width/2;
yourImageView.layer.masksToBounds = YES;
First give equal width and equal height for UIImageView, then make use it
imgView.layer.cornerRadius = imgView.frame.size.height / 2.0;
imgView.layer.masksToBounds = YES;

Scale image based off UIPinch

I am so confused with how to do what I need! Any help is very appreciated! I have an transparent image overlay that has another image behind it that has a UIPanGestureRecognizer and a UIPinchGestureRecognizer attached to it. The top image has a completely transparent middle that serves as the "glass" view. I am trying to crop the bottom image to the "glass" part based off pan and pinch. (see the image below to see what i'm talking about) I have successfully taken care of the pan crop but am having issues cropping it correctly when pinch is also applied.
I am not using snapshotViewAfterScreenUpdates.
Here is the code I have so far:
UIImage *snapshotImage;
/* draw the image of all the views below our view */
UIGraphicsBeginImageContextWithOptions(self.pendantImageView.bounds.size, NO, 0);
BOOL successfulDrawHierarchy = [self.pendantImageView drawViewHierarchyInRect:self.pendantImageView.bounds afterScreenUpdates:YES];
if ( successfulDrawHierarchy ) {
snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
} else {
NSLog(#"drawViewHierarchyInRect:afterScreenUpdates: failed - there's nothing to draw...");
}
UIGraphicsEndImageContext();
UIImage *croppedImage;
if ( successfulDrawHierarchy ) {
/* calculate the coordinates of the rectangle we're interested in within the returned image */
CGRect cropRect = CGRectOffset(pendantFrame, - self.pendantImageView.frame.origin.x, - self.pendantImageView.frame.origin.y);
/* draw the cropped section with a clipping region */
UIGraphicsBeginImageContextWithOptions(cropRect.size, YES, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClipToRect(context, CGRectMake(0, 0, cropRect.size.width, cropRect.size.height));
CGRect targetRectangeForCrop = CGRectMake(-cropRect.origin.x, -cropRect.origin.y, snapshotImage.size.width, snapshotImage.size.height);
[snapshotImage drawInRect:targetRectangeForCrop];
croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
pendantImageView is the bottom imageView and pendantFrame is the middle coords of the area i'm trying to crop to.
Thanks in advance!
I am not sure whether I understood you correctly, but here is my result:
(click on the video to watch full version)
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIImageView *theImageView = [UIImageView new];
[self.view addSubview:theImageView];
theImageView.image = [UIImage imageNamed:#"image1.jpg"];
theImageView.frame = self.view.frame;
theImageView.userInteractionEnabled = YES;
theImageView.alpha = 0.6;
UIImageView *thePanImageView = [UIImageView new];
[self.view addSubview:thePanImageView];
thePanImageView.frame = CGRectMake(0, 0, 100, 100);
thePanImageView.center = CGPointMake(theImageView.frame.size.width/2, theImageView.frame.size.height/2);
thePanImageView.image = [self screenshotFromRect:thePanImageView.frame fromView:theImageView];
thePanImageView.userInteractionEnabled = YES;
thePanImageView.layer.cornerRadius = thePanImageView.frame.size.width/2;
thePanImageView.clipsToBounds = YES;
thePanImageView.theWeakObject = theImageView;
{
UIPanGestureRecognizer *thePanGesture = [UIPanGestureRecognizer new];
[thePanGesture addTarget:self action:#selector(handlePanGesture:)];
[thePanImageView addGestureRecognizer:thePanGesture];
UIPinchGestureRecognizer *thePinchGesture = [UIPinchGestureRecognizer new];
[thePinchGesture addTarget:self action:#selector(handlePinchGesture:)];
[thePanImageView addGestureRecognizer:thePinchGesture];
}
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)thePanGesture
{
UIImageView *thePanImageView = (id)thePanGesture.view;
UIImageView *theImageView = thePanImageView.theWeakObject;
thePanImageView.center = [thePanGesture locationInView:theImageView];
thePanImageView.image = [self screenshotFromRect:thePanImageView.frame fromView:theImageView];
}
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)thePinchGesture
{
UIImageView *thePanImageView = (id)thePinchGesture.view;
static CGRect theInitialFrame;
if (thePinchGesture.state == UIGestureRecognizerStateBegan)
{
theInitialFrame = thePanImageView.frame;
}
else
{
CGRect theFrame = theInitialFrame;
theFrame.size.width *= thePinchGesture.scale;
theFrame.size.height *= thePinchGesture.scale;
thePanImageView.frame = theFrame;
}
thePanImageView.layer.cornerRadius = thePanImageView.frame.size.width/2;
UIImageView *theImageView = thePanImageView.theWeakObject;
thePanImageView.center = [thePinchGesture locationInView:theImageView];
thePanImageView.image = [self screenshotFromRect:thePanImageView.frame fromView:theImageView];
}
- (UIImage * __nonnull)screenshotFromRect:(CGRect)theRect fromView:(UIView * __nonnull)theView;
{
if (!theView)
{
abort();
}
if (theRect.size.height < 1 || theRect.size.width < 1)
{
abort();
}
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)])
{
UIGraphicsBeginImageContextWithOptions(theRect.size, YES, [UIScreen mainScreen].scale);
}
else
{
UIGraphicsBeginImageContext(theRect.size);
}
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(ctx, -theRect.origin.x, -theRect.origin.y);
[theView.layer renderInContext:ctx];
UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshotImage;
}
EDIT:
The above solution is inefficient in terms of CPU, because it takes screenshots every time you move the view.
A much more efficient way would be to create an extra UIImageView, and simply move it inside of your thePanImageView
The code is the following:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIImageView *theImageView = [UIImageView new];
[self.view addSubview:theImageView];
theImageView.image = [UIImage imageNamed:#"image1.jpg"];
theImageView.frame = self.view.frame;
theImageView.userInteractionEnabled = YES;
theImageView.alpha = 0.6;
UIImageView *thePanImageView = [UIImageView new];
[self.view addSubview:thePanImageView];
thePanImageView.frame = CGRectMake(0, 0, 100, 100);
thePanImageView.center = CGPointMake(theImageView.frame.size.width/2, theImageView.frame.size.height/2);
thePanImageView.userInteractionEnabled = YES;
thePanImageView.layer.cornerRadius = thePanImageView.frame.size.width/2;
thePanImageView.clipsToBounds = YES;
thePanImageView.layer.borderColor = [UIColor redColor].CGColor;
thePanImageView.layer.borderWidth = 1;
thePanImageView.theWeakObject = theImageView;
{
UIPanGestureRecognizer *thePanGesture = [UIPanGestureRecognizer new];
[thePanGesture addTarget:self action:#selector(handlePanGesture:)];
[thePanImageView addGestureRecognizer:thePanGesture];
UIPinchGestureRecognizer *thePinchGesture = [UIPinchGestureRecognizer new];
[thePinchGesture addTarget:self action:#selector(handlePinchGesture:)];
[thePanImageView addGestureRecognizer:thePinchGesture];
UIImageView *theExtraImageView = [UIImageView new];
[thePanImageView addSubview:theExtraImageView];
theExtraImageView.frame = CGRectMake(-thePanImageView.frame.origin.x, -thePanImageView.frame.origin.y, theImageView.frame.size.width, theImageView.frame.size.height);
theExtraImageView.image = theImageView.image;
}
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)thePanGesture
{
UIImageView *thePanImageView = (id)thePanGesture.view;
UIImageView *theImageView = thePanImageView.theWeakObject;
thePanImageView.center = [thePanGesture locationInView:theImageView];
UIImageView *theExtraImageView = thePanImageView.subviews.firstObject;
theExtraImageView.frame = CGRectMake(-thePanImageView.frame.origin.x, -thePanImageView.frame.origin.y, theExtraImageView.frame.size.width, theExtraImageView.frame.size.height);
}
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)thePinchGesture
{
UIImageView *thePanImageView = (id)thePinchGesture.view;
static CGRect theInitialFrame;
if (thePinchGesture.state == UIGestureRecognizerStateBegan)
{
theInitialFrame = thePanImageView.frame;
}
else
{
CGRect theFrame = theInitialFrame;
theFrame.size.width *= thePinchGesture.scale;
theFrame.size.height *= thePinchGesture.scale;
thePanImageView.frame = theFrame;
}
thePanImageView.layer.cornerRadius = thePanImageView.frame.size.width/2;
UIImageView *theImageView = thePanImageView.theWeakObject;
thePanImageView.center = [thePinchGesture locationInView:theImageView];
UIImageView *theExtraImageView = thePanImageView.subviews.firstObject;
theExtraImageView.frame = CGRectMake(-thePanImageView.frame.origin.x, -thePanImageView.frame.origin.y, theExtraImageView.frame.size.width, theExtraImageView.frame.size.height);
}
FYI:
theWeakObject is just my custom property to NSObject. I used it because I was lazy and didn't want to create globally-visible #property
you should ask for how to output a image use mask! here is the link for the solution:
CGContextClipToMask returning blank image
you need to change:
CGContextClipToRect(context, CGRectMake(0, 0, cropRect.size.width, cropRect.size.height));
to something like:
CGContextClipToMask(context, CGRectMake(0, 0, cropRect.size.width, cropRect.size.height), maskImage);

Is it possible to position views on top of each other

I am building an Watch app where I want to overlay WKInterfaceImage with a group with a bunch of WKInterfaceLabel objects. Can't seem to be able to do this in StoryBoard editor.
Has anyone ever been able to achieve laying out views on top of each other for Watch App?
PS. I am aware of WKInterfaceGroup setBackgroundImage method. Since I want to do some animation inside WKInterfaceImage, setBackgroundImage is not going to woe for me
You can't layout WKInterfaceObjects on top of each other. You can render the labels over your image and then set it. You will have to render the labels for each frame of the animation. I do this in my watch app for buttons. I generate an image that is a large piece of my UI, I then generate each frame of the animations and overlay the button text for each frame. Then a cut up the image for each frame so that I can the animations on each of the buttons. When you use the app it looks like the image is animating under the buttons, when in actuality it is 4 different animations in 4 different buttons.
Edit
I figured I would add some more detail. Here is a screen capture of my app. I am guessing that you want to do something similar.
First in my storyboard you need to make sure that the spacing for your groups in zero.
To create this UI I use this helper class. I have edited this class to focus on just the needed parts.
typedef struct
{
CGRect top;
CGRect left;
CGRect right;
CGRect bottom;
} ButtonRects;
#interface GPWatchMapImage ()
#property (readwrite, nonatomic) UIImage* topImage;
#property (readwrite, nonatomic) UIImage* bottomImage;
#property (readwrite, nonatomic) UIImage* leftImage;
#property (readwrite, nonatomic) UIImage* rightImage;
#property (readwrite, nonatomic) CGRect topRect;
#property (readwrite, nonatomic) CGRect bottomRect;
#property (readwrite, nonatomic) CGRect leftRect;
#property (readwrite, nonatomic) CGRect rightRect;
#property (readwrite, nonatomic) UIImage* fullImageWithoutArrows;
#property BOOL addedArrows;
#end
#implementation GPWatchMapImage
-(instancetype)init
{
self = [super init];
if (self)
{
}
return self;
}
-(UIImage*)leftImage
{
if (_leftImage == nil)
{
[self breakUpForButtons];
}
return _leftImage;
}
-(UIImage*)rightImage
{
if (_rightImage == nil)
{
[self breakUpForButtons];
}
return _rightImage;
}
-(UIImage*)topImage
{
if (_topImage == nil)
{
[self breakUpForButtons];
}
return _topImage;
}
-(UIImage*)bottomImage
{
if (_bottomImage == nil)
{
[self breakUpForButtons];
}
return _bottomImage;
}
-(UIImage*)fullImageWithoutArrows
{
[self fullImage]; //make sure we have the full image
if (_fullImageWithoutArrows != nil)
{
return _fullImageWithoutArrows;
}
return _fullImage;
}
-(UIImage*)fullImage
{
if (_fullImage == nil)
{
//This is the rect to create the image in
CGRect rect = CGRectMake(0, 0, self.watchSize.width, self.watchSize.height);
//This is how I generate map images. You will need to do something else
self.generatedMapInfo = [[GPCustomMapMaker instance] makeCustomMapFromConfig:self.mapConfig];
_fullImage = self.generatedMapInfo.mapImage;
}
if (self.showMapArrows && !self.addedArrows)
{
//Add the arrows
[self addButtonArrowsToFullImage];
}
return _fullImage;
}
-(void)addButtonArrowsToFullImage
{
self.addedArrows = YES;
ButtonRects rects = [self buttonRects];
UIImage* img = self.fullImage;
self.fullImageWithoutArrows = img; //save for animations
UIGraphicsBeginImageContext(img.size);
UIColor* color = [UIColor colorWithRed:.4 green:.4 blue:.4 alpha:.6];
//CGSize arrowSize = CGSizeMake(24, 4);
CGSize arrowSize = CGSizeMake(48, 8);
CGFloat edgeOffset = 26;
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 6);
CGContextSetLineJoin(ctx, kCGLineJoinRound);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetStrokeColorWithColor(ctx, color.CGColor);
[img drawAtPoint:CGPointMake(0, 0)];
//Left arrow
CGPoint leftCenter = CGPointMake(rects.left.origin.x + edgeOffset, rects.left.origin.y + rects.left.size.height/2);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, leftCenter.x + arrowSize.height, leftCenter.y - arrowSize.width/2);
CGContextAddLineToPoint(ctx, leftCenter.x, leftCenter.y);
CGContextAddLineToPoint(ctx, leftCenter.x + arrowSize.height, leftCenter.y + arrowSize.width/2);
CGContextStrokePath(ctx);
CGPoint rightCenter = CGPointMake(rects.right.origin.x + rects.right.size.width - edgeOffset, rects.right.origin.y + rects.right.size.height/2);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, rightCenter.x, rightCenter.y - arrowSize.width/2);
CGContextAddLineToPoint(ctx, rightCenter.x + arrowSize.height, rightCenter.y);
CGContextAddLineToPoint(ctx, rightCenter.x, rightCenter.y + arrowSize.width/2);
CGContextStrokePath(ctx);
CGPoint topCenter = CGPointMake(rects.top.origin.x + rects.top.size.width/2, rects.top.origin.y + edgeOffset);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, topCenter.x - arrowSize.width/2, topCenter.y + arrowSize.height);
CGContextAddLineToPoint(ctx, topCenter.x, topCenter.y);
CGContextAddLineToPoint(ctx, topCenter.x + arrowSize.width/2, topCenter.y + arrowSize.height);
CGContextStrokePath(ctx);
CGPoint bottomCenter = CGPointMake(rects.bottom.origin.x + rects.bottom.size.width/2, rects.bottom.origin.y + rects.bottom.size.height - edgeOffset);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, bottomCenter.x - arrowSize.width/2, bottomCenter.y);
CGContextAddLineToPoint(ctx, bottomCenter.x, bottomCenter.y + arrowSize.height);
CGContextAddLineToPoint(ctx, bottomCenter.x + arrowSize.width/2, bottomCenter.y);
CGContextStrokePath(ctx);
UIImage* imgWithButtons = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
_fullImage = imgWithButtons;
}
-(UIImage*)subImageInRect:(CGRect)rect fromImage:(UIImage*)image
{
UIGraphicsBeginImageContext(rect.size);
[image drawInRect:CGRectMake(-rect.origin.x, -rect.origin.y, image.size.width, image.size.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
-(ButtonRects)buttonRects
{
UIImage* img = self.fullImage;
CGSize size = self.watchSize;
CGFloat topHeight = size.height * .25;
CGFloat bottomHeight = size.height * .25;
CGFloat middleHeight = size.height * .5;
CGRect topRect = CGRectMake(0, 0, size.width, topHeight);
CGRect leftRect = CGRectMake(0, topHeight, img.size.width/2.0, middleHeight);
CGRect rightRect = CGRectMake(img.size.width/2.0, topHeight, img.size.width/2.0, middleHeight);
CGRect bottomRect = CGRectMake(0, topHeight + middleHeight, size.width, bottomHeight);
ButtonRects rects;
rects.top = topRect;
rects.bottom = bottomRect;
rects.left = leftRect;
rects.right = rightRect;
return rects;
}
-(void)breakUpForButtons
{
UIImage* img = self.fullImage;
ButtonRects rects = [self buttonRects];
_topImage = [self subImageInRect:rects.top fromImage:img];
_leftImage = [self subImageInRect:rects.left fromImage:img];
_rightImage = [self subImageInRect:rects.right fromImage:img];
_bottomImage = [self subImageInRect:rects.bottom fromImage:img];
}
#end
Next in my WKInterfaceController class I do this to make the animations.
typedef NS_ENUM(NSInteger, GPWatchImageAnimation)
{
GPWatchImageAnimationNone,
GPWatchImageAnimationSlideLeftToRight,
GPWatchImageAnimationSlideRighToLeft,
GPWatchImageAnimationSlideTopToBottom,
GPWatchImageAnimationSlideBottomToTop,
GPWatchImageAnimationZoomIn,
GPWatchImageAnimationZoomOut
};
#define kNumImagesInMapAnimations 6
#define kMapAnimatinonDuration 0.4
...
-(void)updateMapImageWithAnimation:(GPWatchImageAnimation)animation
{
GPWatchMapImage* previousImage = self.currentMapImage;
//This gets the size of the full image that you want
CGSize size = [self mapSize];
self.currentMapImage = [[GPWatchMapImage alloc] init];
self.currentMapImage.watchSize = size;
//Check if we have a previous image to animate from
if (previousImage != nil && animation != GPWatchImageAnimationNone)
{
NSDictionary* animatedImage = [self animateFromImage:previousImage toImage:self.currentMapImage withAnimation:animation];
[self.mapTopImage setImage:[animatedImage objectForKey:#"top"]];
[self.mapLeftImage setImage:[animatedImage objectForKey:#"left"]];
[self.mapRightImage setImage:[animatedImage objectForKey:#"right"]];
[self.mapBottomImage setImage:[animatedImage objectForKey:#"bottom"]];
[self.mapTopImage startAnimatingWithImagesInRange:NSMakeRange(0, kNumImagesInMapAnimations) duration:kMapAnimatinonDuration repeatCount:1];
[self.mapLeftImage startAnimatingWithImagesInRange:NSMakeRange(0, kNumImagesInMapAnimations) duration:kMapAnimatinonDuration repeatCount:1];
[self.mapRightImage startAnimatingWithImagesInRange:NSMakeRange(0, kNumImagesInMapAnimations) duration:kMapAnimatinonDuration repeatCount:1];
[self.mapBottomImage startAnimatingWithImagesInRange:NSMakeRange(0, kNumImagesInMapAnimations) duration:kMapAnimatinonDuration repeatCount:1];
}
else
{
[self.mapTopImage setImage:self.currentMapImage.topImage];
[self.mapLeftImage setImage:self.currentMapImage.leftImage];
[self.mapRightImage setImage:self.currentMapImage.rightImage];
[self.mapBottomImage setImage:self.currentMapImage.bottomImage];
}
}
-(NSDictionary*)animateFromImage:(GPWatchMapImage*)fromImage toImage:(GPWatchMapImage*)toImage withAnimation:(GPWatchImageAnimation)animation
{
NSMutableArray* topAnimatedImages = [[NSMutableArray alloc] initWithCapacity:kNumImagesInMapAnimations];
NSMutableArray* bottomAnimatedImages = [[NSMutableArray alloc] initWithCapacity:kNumImagesInMapAnimations];
NSMutableArray* leftAnimatedImages = [[NSMutableArray alloc] initWithCapacity:kNumImagesInMapAnimations];
NSMutableArray* rightAnimatedImages = [[NSMutableArray alloc] initWithCapacity:kNumImagesInMapAnimations];
CGSize size = fromImage.fullImage.size;
for (int step = 0; step < kNumImagesInMapAnimations; step++)
{
UIGraphicsBeginImageContext(size);
//render this step
if (animation == GPWatchImageAnimationSlideLeftToRight)
{
CGFloat stepSize = (size.width/2)/kNumImagesInMapAnimations;
CGPoint fromPoint = CGPointMake(-(step+1)*stepSize, 0);
CGPoint toPoint = CGPointMake(size.width/2 - (step+1)*stepSize, 0);
[fromImage.fullImageWithoutArrows drawAtPoint:fromPoint];
[toImage.fullImageWithoutArrows drawAtPoint:toPoint];
}
else if (animation == GPWatchImageAnimationSlideRighToLeft)
{
CGFloat stepSize = (size.width/2)/kNumImagesInMapAnimations;
CGPoint fromPoint = CGPointMake((step+1)*stepSize, 0);
CGPoint toPoint = CGPointMake(-size.width/2 + (step+1)*stepSize, 0);
[fromImage.fullImageWithoutArrows drawAtPoint:fromPoint];
[toImage.fullImageWithoutArrows drawAtPoint:toPoint];
}
else if (animation == GPWatchImageAnimationSlideTopToBottom)
{
CGFloat stepSize = (size.height/2)/kNumImagesInMapAnimations;
CGPoint fromPoint = CGPointMake(0, (step+1)*stepSize);
CGPoint toPoint = CGPointMake(0, -size.height/2 + (step+1)*stepSize);
[fromImage.fullImageWithoutArrows drawAtPoint:fromPoint];
[toImage.fullImageWithoutArrows drawAtPoint:toPoint];
}
else if (animation == GPWatchImageAnimationSlideBottomToTop)
{
CGFloat stepSize = (size.height/2)/kNumImagesInMapAnimations;
CGPoint fromPoint = CGPointMake(0, -(step+1)*stepSize);
CGPoint toPoint = CGPointMake(0, size.height/2 - (step+1)*stepSize);
[fromImage.fullImageWithoutArrows drawAtPoint:fromPoint];
[toImage.fullImageWithoutArrows drawAtPoint:toPoint];
}
else if (animation == GPWatchImageAnimationZoomOut)
{
CGFloat yStepSize = (size.height/2)/kNumImagesInMapAnimations;
CGFloat xStepSize = (size.width/2)/kNumImagesInMapAnimations;
//CGRect fromRect = CGRectMake((step + 1)*xStepSize, (step + 1)*yStepSize, size.width - 2*(step + 1)*xStepSize, size.height - 2*(step + 1)*yStepSize);
CGRect toRect = CGRectMake(-size.width/2 + (step+1)*xStepSize, -size.height/2 + (step+1)*yStepSize, size.width + 2*(kNumImagesInMapAnimations - step - 1)*xStepSize, size.height + 2*(kNumImagesInMapAnimations - step - 1)*yStepSize);
[toImage.fullImageWithoutArrows drawInRect:toRect];
//double alpha = (double)(kNumImagesInMapAnimations - step - 1)/(double)kNumImagesInMapAnimations;
//CGContextSetAlpha(UIGraphicsGetCurrentContext(), alpha);
//[fromImage.fullImageWithoutArrows drawInRect:fromRect];
//CGContextSetAlpha(UIGraphicsGetCurrentContext(), 1.0);
}
else if (animation == GPWatchImageAnimationZoomIn)
{
if (step == kNumImagesInMapAnimations -1)
{
[toImage.fullImageWithoutArrows drawAtPoint:CGPointMake(0, 0)];
}
else
{
CGFloat yStepSize = (size.height/2)/kNumImagesInMapAnimations;
CGFloat xStepSize = (size.width/2)/kNumImagesInMapAnimations;
CGRect fromRect = CGRectMake(-(step + 1)*xStepSize, -(step + 1)*yStepSize, size.width + 2*(step + 1)*xStepSize, size.height + 2*(step + 1)*yStepSize);
//CGRect toRect = CGRectMake(-size.width/2 + (step+1)*xStepSize, -size.height/2 + (step+1)*yStepSize, size.width + 2*(kNumImagesInMapAnimations - step - 1)*xStepSize, size.height + 2*(kNumImagesInMapAnimations - step - 1)*yStepSize);
[fromImage.fullImageWithoutArrows drawInRect:fromRect];
//[toImage.fullImageWithoutArrows drawInRect:fromRect];
}
}
else
{
[toImage.fullImageWithoutArrows drawAtPoint:CGPointMake(0, 0)];
}
UIImage* stepImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//Get each of the pieces of the image
GPWatchMapImage* mapImage = [[GPWatchMapImage alloc] init];
mapImage.showMapArrows = self.showMapArrows;
mapImage.fullImage = stepImg;
mapImage.watchSize = [self mapSize];
[topAnimatedImages addObject:mapImage.topImage];
[bottomAnimatedImages addObject:mapImage.bottomImage];
[leftAnimatedImages addObject:mapImage.leftImage];
[rightAnimatedImages addObject:mapImage.rightImage];
}
UIImage* topAnimatedImage = [UIImage animatedImageWithImages:topAnimatedImages duration:kMapAnimatinonDuration];
UIImage* bottomAnimatedImage = [UIImage animatedImageWithImages:bottomAnimatedImages duration:kMapAnimatinonDuration];
UIImage* leftAnimatedImage = [UIImage animatedImageWithImages:leftAnimatedImages duration:kMapAnimatinonDuration];
UIImage* rightAnimatedImage = [UIImage animatedImageWithImages:rightAnimatedImages duration:kMapAnimatinonDuration];
return #{#"top": topAnimatedImage, #"bottom": bottomAnimatedImage, #"left": leftAnimatedImage, #"right": rightAnimatedImage};
}
In InterfaceBuilder: You can just use a Group and set a Backgroundimage from your assets.
When you then add an WKInterfaceImage into that group you have two layers to work with (if two layers are enough for you).

iOS drag shape pinned to center

Taking a next step from this post Drag UIView around Shape Comprised of CGMutablePaths , I am trying to add a line which is on one end pinned to the center of the pathLayer_ and the other end gets dragged along with the circular object (circleLayer in handleView_) on the path (figure-8).
The line has its own layer andview and path initiated in (void)initHandleView.
First: I am not able to get the line to go through the center of the pathLayer_:
- (void)viewDidLoad {
[super viewDidLoad];
[self initPathLayer];
[self initHandleView];
[self initHandlePanGestureRecognizer];
}
- (void)initPathLayer {
pathLayer_ = [CAShapeLayer layer];
pathLayer_.lineWidth = 1;
pathLayer_.fillColor = nil;
pathLayer_.strokeColor = [UIColor lightGrayColor].CGColor;
pathLayer_.lineCap = kCALineCapButt;
pathLayer_.lineJoin = kCALineJoinRound;
pathLayer_.frame = self.view.bounds;
pathLayerCenter_ = CGPointMake(CGRectGetMidX(pathLayer_.frame), CGRectGetMidY(pathLayer_.frame));
[self.view.layer addSublayer:pathLayer_];
}
- (void)initHandleView {
handlePathPointIndex_ = 0;
CGRect rect = CGRectMake(0, 0, 30, 30);
CAShapeLayer *circleLayer = [CAShapeLayer layer];
circleLayer.fillColor = [UIColor redColor].CGColor;
circleLayer.strokeColor = [UIColor redColor].CGColor;
circleLayer.lineWidth = 2;
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(rect, circleLayer.lineWidth, circleLayer.lineWidth)];
circleLayer.frame = rect;
circleLayer.path = circlePath.CGPath;
handleView_ = [[UIView alloc] initWithFrame:rect];
CGRect lineRect = CGRectMake(0,0,CGRectGetMaxX([[UIScreen mainScreen] bounds]), CGRectGetMaxY([[UIScreen mainScreen] bounds]));
CGPoint center = CGPointMake(CGRectGetMidX(pathLayer_.frame), CGRectGetMidY(pathLayer_.frame));
lineView_ = [[UIView alloc] initWithFrame:lineRect];
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.fillColor = [UIColor blueColor].CGColor;
lineLayer.strokeColor = [UIColor blueColor].CGColor;
lineLayer.lineWidth = 2;
lineLayer.frame = self.view.bounds;
UIBezierPath *linePath = [UIBezierPath bezierPath];
CGPoint circlePoint = CGPointMake(handleView_.center.x, handleView_.center.y);
[linePath moveToPoint:center]; //Center
[linePath addLineToPoint:circlePoint];
[linePath moveToPoint:circlePoint]; //Handle
lineLayer.path = linePath.CGPath;
[handleView_.layer insertSublayer:circleLayer above:handleView_.layer];
[handleView_.layer insertSublayer:lineLayer above:handleView_.layer];
// handleView_.layer.masksToBounds = YES;
float direction = DEGREES_TO_RADIANS(10);
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(direction);
[handleView_ setTransform:rotationTransform];
[self.view addSubview:lineView_];
[self.view addSubview:handleView_];
}
And second: I am not sure I do the right thing with the PanGesturerecognizer:
- (void)handleWasPanned:(UIPanGestureRecognizer *)recognizer {
switch (recognizer.state) {
case UIGestureRecognizerStateBegan: {
desiredHandleCenter_ = handleView_.center;
break;
}
case UIGestureRecognizerStateChanged:
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
CGPoint translation = [recognizer translationInView:self.view];
desiredHandleCenter_.x += translation.x;
desiredHandleCenter_.y += translation.y;
[self moveHandleTowardPointAndRotateLine:desiredHandleCenter_];
break;
}
default:
break;
}
[recognizer setTranslation:CGPointZero inView:self.view];
}
- (void)moveHandleTowardPointAndRotateLine:(CGPoint)point {
CGFloat earlierDistance = [self distanceToPoint:point ifHandleMovesByOffset:-1];
CGFloat currentDistance = [self distanceToPoint:point ifHandleMovesByOffset:0];
CGFloat laterDistance = [self distanceToPoint:point ifHandleMovesByOffset:1];
if (currentDistance <= earlierDistance && currentDistance <= laterDistance)
return;
NSInteger step;
CGFloat distance;
if (earlierDistance < laterDistance) {
step = -1;
distance = earlierDistance;
} else {
step = 1;
distance = laterDistance;
}
NSInteger offset = step;
while (true) {
NSInteger nextOffset = offset + step;
CGFloat nextDistance = [self distanceToPoint:point ifHandleMovesByOffset:nextOffset];
if (nextDistance >= distance)
break;
distance = nextDistance;
offset = nextOffset;
}
handlePathPointIndex_ += offset;
// Make one end of the line move with handle (point) while the other is pinned to center of pathLayer_ (ie. in figure8 its the cross point, in a circle it's center)
//CGFloat rot = [self getTouchAngle:point];
CGFloat rot = atan2f((point.x - pathLayerCenter_.x), -(point.y - pathLayerCenter_.y));
handleView_.layer.transform = CATransform3DMakeRotation(rot, 0., 0., 1.);
[self layoutHandleView];
}
I'm just going to ignore your code and tell you how I'd modify my original answer to add the line you want.
First, I'm going to use a dedicated shape layer for the line, so I'll add an instance variable to reference the line layer:
#implementation ViewController {
CAShapeLayer *lineLayer_; // NEW!!!
UIBezierPath *path_;
CAShapeLayer *pathLayer_;
etc.
I need to initialize the line layer when I load the view:
- (void)viewDidLoad {
[super viewDidLoad];
[self initPathLayer];
[self initHandleView];
[self initHandlePanGestureRecognizer];
[self initLineLayer]; // NEW!!!
}
- (void)initLineLayer {
lineLayer_ = [CAShapeLayer layer];
lineLayer_.lineWidth = 1;
lineLayer_.fillColor = nil;
lineLayer_.strokeColor = [UIColor redColor].CGColor;
lineLayer_.lineCap = kCALineCapRound;
lineLayer_.lineJoin = kCALineJoinRound;
[self.view.layer addSublayer:lineLayer_];
}
I need to lay out the line layer when I lay out my top-level view (when it's first shown and on rotations):
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self createPath];
[self createPathPoints];
[self layoutPathLayer];
[self layoutHandleView];
[self layoutLineLayer]; // NEW!!!
}
- (void)layoutLineLayer {
lineLayer_.frame = self.view.bounds;
CGRect bounds = self.view.bounds;
CGPoint start = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGPoint end = pathPoints_[handlePathPointIndex_];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:start];
[path addLineToPoint:end];
lineLayer_.path = path.CGPath;
}
Finally, I need to lay out the line layer again (to update its path) whenever I update the handle:
- (void)moveHandleTowardPoint:(CGPoint)point {
// I have omitted most of this method body for brevity.
// Just copy it from the original answer.
...
...
handlePathPointIndex_ += offset;
[self layoutHandleView];
[self layoutLineLayer]; // NEW!!!
}
Result:

Dynamic drawing performane in iOS7 with Core Graphic

I need some advices about drawing in iOS and more specifically about performance drawing. I read a lot of articles about the drawing in iOS7 but I didn't succeed to obtain the correct result.
I have dots than I need to link them in the correct order.
Thanks to my Dot & Elastic classes, I succeed to have this render. I'm very satisfied :
http://d.pr/v/nYzH
This dots represents a card and I need to use a timeline to navigate between each card.
I use iCarousel library for realize this objective. I work like a UITableView except we manage view. Views can be reused etc...
But the problem start there. This is the result :
http://d.pr/v/y7dq
First problem : dots have low resolution.
Second real problem : I got some lags..
You can follow here my files used for draw dot & elastic.
Dot.m file
#interface Dot () {
CAShapeLayer *_foreground;
CAShapeLayer *_background;
UIBezierPath *_path;
}
#end
#implementation Dot
- (id)initWithPoint:(CGPoint)point andRadius:(CGFloat)radius
{
self = [super init];
if (self) {
self.frame = CGRectMake(point.x, point.y, (radius + 10) * 2, (radius + 10) * 2);
self.radius = radius;
self.color = [UIColor colorWithHexString:#"#FFFFFF"];
[self setPosition:point];
[self setupPath];
_background = [CAShapeLayer layer];
[_background setFrame:CGRectMake(0, 0, self.width, self.height)];
[_background setPath:_path.CGPath];
[self.layer addSublayer:_background];
[self drawStrokedDotToLayer:_background];
_foreground = [CAShapeLayer layer];
[_foreground setFrame:CGRectMake(0, 0, self.width, self.height)];
[_foreground setPath:_path.CGPath];
[self.layer addSublayer:_foreground];
[self drawPlainDotToLayer:_foreground];
[self setBackgroundColor:[UIColor clearColor]];
self.state = DotStateHidden;
}
return self;
}
- (void)setupPath
{
_path = [UIBezierPath bezierPath];
[_path addArcWithCenter:CGPointMake(self.width*.5f, self.height*.5f) radius:self.radius startAngle:0 endAngle:M_PI * 2 clockwise:NO];
}
#pragma mark - Setters
- (void)setPosition:(CGPoint)position
{
self.x = position.x - self.width*.5f;
self.y = position.y - self.width*.5f;
}
- (CGPoint)position
{
return CGPointMake(self.x + self.width*.5f, self.y + self.height*.5f);
}
- (void)setState:(DotState)state
{
_state = state;
CAKeyframeAnimation *foregroundAnim = nil;
CAKeyframeAnimation *backgroundAnim = nil;
switch (_state) {
case DotStateFeedback:
{
self.color = [UIColor colorWithHexString:#"#FFFFFF"];
[self drawFeedback:_foreground];
[self removeGlow:_foreground];
foregroundAnim = [self animation:ScaleIn function:ExponentialEaseOut duration:1.f];
break;
}
case DotStateVisible:
{
self.color = [UIColor colorWithHexString:#"#FFFFFF"];
[self drawPlainDotToLayer:_foreground];
[self drawGlow:_foreground];
[self drawStrokedDotToLayer:_background];
foregroundAnim = [self animation:ScaleIn function:ExponentialEaseOut duration:.2f];
break;
}
case DotStateNext:
{
self.color = [UIColor colorWithHexString:#"#FFFFFF"];
[self drawStrokedDotToLayer:_background];
foregroundAnim = [self animation:ScaleOut function:ExponentialEaseOut duration:.16f];
backgroundAnim = [self animation:ScaleIn function:ExponentialEaseOut duration:.25f];
[foregroundAnim setBeginTime:CACurrentMediaTime() + .04f];
break;
}
case DotStateHidden:
default:
{
self.color = [UIColor colorWithHexString:#"#333333"];
[self drawPlainDotToLayer:_foreground];
[self removeGlow:_foreground];
foregroundAnim = [self animation:ScaleIn function:ExponentialEaseOut duration:.5f];
backgroundAnim = [self animation:ScaleOut function:ExponentialEaseOut duration:.25f];
break;
}
}
if (foregroundAnim) [_foreground addAnimation:foregroundAnim forKey:nil];
if (backgroundAnim) [_background addAnimation:backgroundAnim forKey:nil];
}
#pragma mark - Drawing
- (void)drawStrokedDotToLayer:(CAShapeLayer *)layer
{
[layer setLineWidth:2.f];
[layer setStrokeColor:self.color.CGColor];
[layer setFillColor:[UIColor colorWithHexString:#"#1F1F1F"].CGColor];
}
- (void)drawPlainDotToLayer:(CAShapeLayer *)layer
{
[layer setLineWidth:2.f];
[layer setStrokeColor:[UIColor clearColor].CGColor];
[layer setFillColor:self.color.CGColor];
}
- (void)drawFeedback:(CAShapeLayer *)layer
{
[layer setLineWidth:2.f];
[layer setStrokeColor:[UIColor clearColor].CGColor];
[layer setFillColor:[UIColor colorWithHex:0xFFFFFF andAlpha:.025f].CGColor];
}
- (void)drawGlow:(CAShapeLayer *)layer
{
[layer setShadowRadius:8];
[layer setShadowOpacity:1.f];
[layer setShadowOffset:CGSizeMake(0, 0)];
[layer setShadowColor:[UIColor whiteColor].CGColor];
[layer didChangeValueForKey:#"path"];
}
- (void)removeGlow:(CAShapeLayer *)layer
{
[layer setShadowRadius:0];
[layer setShadowOpacity:0.f];
[layer setShadowOffset:CGSizeMake(0, 0)];
[layer setShadowColor:[UIColor clearColor].CGColor];
[layer didChangeValueForKey:#"path"];
}
- (CAKeyframeAnimation *)animation:(NSInteger)name function:(AHEasingFunction)function duration:(CGFloat)duration
{
CAKeyframeAnimation *animation;
switch (name) {
case ScaleIn:
{
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.scale" function:function fromValue:0.f toValue:1.f];
break;
}
case ScaleInFeedback:
{
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.scale" function:function fromValue:0.f toValue:3.f];
break;
}
case ScaleOut:
{
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.scale" function:function fromValue:1.f toValue:0.f];
break;
}
default:
{
animation = [CAKeyframeAnimation animationWithKeyPath:#"opacity" function:function fromValue:0.f toValue:1.f];
break;
}
}
animation.duration = duration;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
return animation;
}
#end
Elastic.m
#interface Elastic () {
UIBezierPath *_path;
UIImage *_image;
}
#end
#implementation Elastic
- (id)initWithDotOne:(Dot *)aDotOne DotTwo:(Dot *)aDotTwo
{
self = [super initWithFrame:[UIScreen mainScreen].bounds];
if (self) {
self.dotOne = aDotOne;
self.dotTwo = aDotTwo;
self.displayDots = NO;
self.feedBack = NO;
self.curveRadius = 4;
[self setBackgroundColor:[UIColor clearColor]];
_path = [UIBezierPath bezierPath];
[self updatePath];
}
return self;
}
- (void)setDisplayDots:(BOOL)displayDots
{
_displayDots = displayDots;
if (_displayDots) {
[self addSubview:self.dotTwo];
} else {
[self.dotTwo removeFromSuperview];
}
}
- (void)drawRect:(CGRect)rect
{
[_image drawInRect:rect];
}
- (void)updatePath
{
// Initialize variables
CGFloat dist = distance(self.dotOne.position, self.dotTwo.position);
CGFloat angle = angleBetweenPoints(self.dotOne.position, self.dotTwo.position) + M_PI * 1.5;
// Points
CGPoint ptA = CGPointMake(
self.dotOne.position.x + cosf(angle) * (self.dotOne.radius - self.curveRadius),
self.dotOne.position.y + sinf(angle) * (self.dotOne.radius - self.curveRadius)
);
CGPoint ptB = CGPointMake(
self.dotOne.position.x + cosf(angle + M_PI) * (self.dotOne.radius - self.curveRadius),
self.dotOne.position.y + sinf(angle + M_PI) * (self.dotOne.radius - self.curveRadius)
);
CGPoint ptC = CGPointMake(
self.dotTwo.position.x + cosf(angle) * (self.dotTwo.radius - self.curveRadius),
self.dotTwo.position.y + sinf(angle) * (self.dotTwo.radius - self.curveRadius)
);
CGPoint ptD = CGPointMake(
self.dotTwo.position.x + cosf(angle + M_PI) * (self.dotTwo.radius - self.curveRadius),
self.dotTwo.position.y + sinf(angle + M_PI) * (self.dotTwo.radius - self.curveRadius)
);
// Bezier
CGFloat mapA = angle + M_PI_2 + map(dist, 150, 350, 0.0001, 0.0005);
CGFloat mapB = angle + M_PI_2 - map(dist, 150, 350, 0.0001, 0.0005);
CGPoint bzA = CGPointMake(self.dotOne.position.x + cosf(mapA) * dist*.5f, self.dotOne.position.y + sinf(mapA) * dist*.5f);
CGPoint bzB = CGPointMake(self.dotOne.position.x + cosf(mapB) * dist*.5f, self.dotOne.position.y + sinf(mapB) * dist*.5f);
// Start drawing path
[_path moveToPoint:ptA];
[_path addQuadCurveToPoint:ptC controlPoint:bzA];
[_path addLineToPoint:ptD];
[_path addQuadCurveToPoint:ptB controlPoint:bzB];
[self drawBitmap];
[self setNeedsDisplay];
}
- (void)drawBitmap
{
_image = nil;
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
[_image drawAtPoint:CGPointZero];
[[UIColor whiteColor] setFill];
[_path fill];
_image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[_path removeAllPoints];
}
#end
Your videos look as if they were recorded on the simulator, not the device. Simulator testing is meaningless for performance, particularly graphics performance, since a dedicated GPU is not available to the simulator.
GPU-heavy operations will often be considerably worse on the simulator than the device for this reason.
So, I can offer you the following advice :
Run performance testing on a device, ideally the slowest one you are targeting.
Use the core animation instrument which will give you frame rates and time profiling, and will highlight the expensive areas of your code.
Investigate the various graphical debugging options available via instruments on the device as well - offscreen rendering, redrawing and so forth
If you like iCarousel, then the author of that project, Nick Lockwood, has an excellent book on core animation for iOS, which contains a chapter about performance tuning.

Resources