How can i create vertical Flip View Animation in ios? - ios

I don't know how to flip my uiview vertically , i have many views and i every view
there are some photos and their description, i want a flip view like a book but not
left to right it must be top to bottom or bottom to top,
i want to flip the whole page vertically like top to bottom or bottom to top,
how to do this kind of work in ios ?
i m seraching on google but thats not working i'm new in developement
So please kindly anyone can guide me properly how can i flip my views Please Please help me
out
Thanks in advance.

download the code from https://github.com/mtabini/AFKPageFlipper
and change the AFKPageFlipper.h and AFKPageFlipper.m file
//
// AFKPageFlipper.h
// AFKPageFlipper
//
// Created by Marco Tabini on 10-10-11.
// Copyright 2010 AFK Studio Partnership. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#class AFKPageFlipper;
#protocol AFKPageFlipperDataSource
- (NSInteger) numberOfPagesForPageFlipper:(AFKPageFlipper *) pageFlipper;
- (UIView *) viewForPage:(NSInteger) page inFlipper:(AFKPageFlipper *) pageFlipper;
#end
typedef enum {
AFKPageFlipperDirectionTop,
AFKPageFlipperDirectionBottom,
} AFKPageFlipperDirection;
#interface AFKPageFlipper : UIView {
NSObject <AFKPageFlipperDataSource> *dataSource;
NSInteger currentPage;
NSInteger numberOfPages;
UIView *currentView;
UIView *nextView;
CALayer *backgroundAnimationLayer;
CALayer *flipAnimationLayer;
AFKPageFlipperDirection flipDirection;
float startFlipAngle;
float endFlipAngle;
float currentAngle;
BOOL setNextViewOnCompletion;
BOOL animating;
BOOL disabled;
}
#property (nonatomic,retain) NSObject <AFKPageFlipperDataSource> *dataSource;
#property (nonatomic,assign) NSInteger currentPage;
#property (nonatomic, retain) UITapGestureRecognizer *tapRecognizer;
#property (nonatomic, retain) UIPanGestureRecognizer *panRecognizer;
#property (nonatomic,assign) BOOL disabled;
- (void) setCurrentPage:(NSInteger) value animated:(BOOL) animated;
#end
//
// AFKPageFlipper.m
// AFKPageFlipper
//
// Created by Marco Tabini on 10-10-12.
// Copyright 2010 AFK Studio Partnership. All rights reserved.
//
#import "AFKPageFlipper.h"
#pragma mark -
#pragma mark UIView helpers
#interface UIView(Extended)
- (UIImage *) imageByRenderingView;
#end
#implementation UIView(Extended)
- (UIImage *) imageByRenderingView {
CGFloat oldAlpha = self.alpha;
self.alpha = 1;
UIGraphicsBeginImageContext(self.bounds.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.alpha = oldAlpha;
return resultingImage;
}
#end
#pragma mark -
#pragma mark Private interface
#interface AFKPageFlipper()
#property (nonatomic,assign) UIView *currentView;
#property (nonatomic,assign) UIView *nextView;
#end
#implementation AFKPageFlipper
#synthesize tapRecognizer = _tapRecognizer;
#synthesize panRecognizer = _panRecognizer;
#pragma mark -
#pragma mark Flip functionality
- (void) initFlip {
// Create screenshots of view
UIImage *currentImage = [self.currentView imageByRenderingView];
UIImage *newImage = [self.nextView imageByRenderingView];
// Hide existing views
self.currentView.alpha = 0;
self.nextView.alpha = 0;
// Create representational layers
CGRect rect = self.bounds;
rect.size.height /= 2;
backgroundAnimationLayer = [CALayer layer];
backgroundAnimationLayer.frame = self.bounds;
backgroundAnimationLayer.zPosition = -300000;
CALayer *topLayer = [CALayer layer];
topLayer.frame = rect;
topLayer.masksToBounds = YES;
topLayer.contentsGravity = kCAGravityBottom;
[backgroundAnimationLayer addSublayer:topLayer];
rect.origin.y = rect.size.height;
CALayer *bottomLayer = [CALayer layer];
bottomLayer.frame = rect;
bottomLayer.masksToBounds = YES;
bottomLayer.contentsGravity = kCAGravityTop;
[backgroundAnimationLayer addSublayer:bottomLayer];
if (flipDirection == AFKPageFlipperDirectionBottom) {
topLayer.contents = (id) [newImage CGImage];
bottomLayer.contents = (id) [currentImage CGImage];
} else {
topLayer.contents = (id) [currentImage CGImage];
bottomLayer.contents = (id) [newImage CGImage];
}
[self.layer addSublayer:backgroundAnimationLayer];
rect.origin.y = 0;
flipAnimationLayer = [CATransformLayer layer];
flipAnimationLayer.anchorPoint = CGPointMake(0.5, 1);
flipAnimationLayer.frame = rect;
[self.layer addSublayer:flipAnimationLayer];
CALayer *backLayer = [CALayer layer];
backLayer.frame = flipAnimationLayer.bounds;
backLayer.doubleSided = NO;
backLayer.masksToBounds = YES;
[flipAnimationLayer addSublayer:backLayer];
CALayer *frontLayer = [CALayer layer];
frontLayer.frame = flipAnimationLayer.bounds;
frontLayer.doubleSided = NO;
frontLayer.masksToBounds = YES;
frontLayer.transform = CATransform3DMakeRotation(M_PI, 1.0, 0.0, 0);
[flipAnimationLayer addSublayer:frontLayer];
if (flipDirection == AFKPageFlipperDirectionBottom) {
backLayer.contents = (id) [currentImage CGImage];
backLayer.contentsGravity = kCAGravityBottom;
frontLayer.contents = (id) [newImage CGImage];
frontLayer.contentsGravity = kCAGravityTop;
CATransform3D transform = CATransform3DMakeRotation(1.1/M_PI, 1.0, 0.0, 0.0);
transform.m34 = 1.0f / 2500.0f;
flipAnimationLayer.transform = transform;
currentAngle = startFlipAngle = 0;
endFlipAngle = M_PI;
} else {
//down
backLayer.contents = (id) [newImage CGImage];
backLayer.contentsGravity = kCAGravityBottom;
frontLayer.contents = (id) [currentImage CGImage];
frontLayer.contentsGravity = kCAGravityTop;
CATransform3D transform = CATransform3DMakeRotation(M_PI/1.1, 1.0, 0.0, 0.0);
transform.m34 = 1.0f / 2500.0f;
flipAnimationLayer.transform = transform;
currentAngle = startFlipAngle = M_PI;
endFlipAngle = 0;
}
}
- (void) cleanupFlip {
[backgroundAnimationLayer removeFromSuperlayer];
[flipAnimationLayer removeFromSuperlayer];
backgroundAnimationLayer = Nil;
flipAnimationLayer = Nil;
animating = NO;
if (setNextViewOnCompletion) {
[self.currentView removeFromSuperview];
self.currentView = self.nextView;
self.nextView = Nil;
} else {
[self.nextView removeFromSuperview];
self.nextView = Nil;
}
self.currentView.alpha = 1;
}
- (void) setFlipProgress:(float) progress setDelegate:(BOOL) setDelegate animate:(BOOL) animate {
if (animate) {
animating = YES;
}
float newAngle = startFlipAngle + progress * (endFlipAngle - startFlipAngle);
float duration = animate ? 0.5 * fabs((newAngle - currentAngle) / (endFlipAngle - startFlipAngle)) : 0;
currentAngle = newAngle;
CATransform3D endTransform = CATransform3DIdentity;
endTransform.m34 = 1.0f / 2500.0f;
endTransform = CATransform3DRotate(endTransform, newAngle, 1.0, 0.0, 0.0);
[flipAnimationLayer removeAllAnimations];
[CATransaction begin];
[CATransaction setAnimationDuration:duration];
flipAnimationLayer.transform = endTransform;
[CATransaction commit];
if (setDelegate) {
[self performSelector:#selector(cleanupFlip) withObject:Nil afterDelay:duration];
}
}
- (void) flipPage {
[self setFlipProgress:1.0 setDelegate:YES animate:YES];
}
#pragma mark -
#pragma mark Animation management
- (void)animationDidStop:(NSString *) animationID finished:(NSNumber *) finished context:(void *) context {
[self cleanupFlip];
}
#pragma mark -
#pragma mark Properties
#synthesize currentView;
- (void) setCurrentView:(UIView *) value {
if (currentView) {
[currentView release];
}
currentView = [value retain];
}
#synthesize nextView;
- (void) setNextView:(UIView *) value {
if (nextView) {
[nextView release];
}
nextView = [value retain];
}
#synthesize currentPage;
- (BOOL) doSetCurrentPage:(NSInteger) value {
if (value == currentPage) {
return FALSE;
}
flipDirection = value < currentPage ? AFKPageFlipperDirectionBottom : AFKPageFlipperDirectionTop;
currentPage = value;
self.nextView = [self.dataSource viewForPage:value inFlipper:self];
[self addSubview:self.nextView];
return TRUE;
}
- (void) setCurrentPage:(NSInteger) value {
if (![self doSetCurrentPage:value]) {
return;
}
setNextViewOnCompletion = YES;
animating = YES;
self.nextView.alpha = 0;
[UIView beginAnimations:#"" context:Nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
self.nextView.alpha = 1;
[UIView commitAnimations];
}
- (void) setCurrentPage:(NSInteger) value animated:(BOOL) animated {
if (![self doSetCurrentPage:value]) {
return;
}
setNextViewOnCompletion = YES;
animating = YES;
if (animated) {
[self initFlip];
[self performSelector:#selector(flipPage) withObject:Nil afterDelay:0.091];
} else {
[self animationDidStop:Nil finished:[NSNumber numberWithBool:NO] context:Nil];
}
}
#synthesize dataSource;
- (void) setDataSource:(NSObject <AFKPageFlipperDataSource>*) value {
if (dataSource) {
[dataSource release];
}
dataSource = [value retain];
numberOfPages = [dataSource numberOfPagesForPageFlipper:self];
currentPage = 0;
self.currentPage = 1;
}
#synthesize disabled;
- (void) setDisabled:(BOOL) value {
disabled = value;
self.userInteractionEnabled = !value;
for (UIGestureRecognizer *recognizer in self.gestureRecognizers) {
recognizer.enabled = !value;
}
}
#pragma mark -
#pragma mark Touch management
- (void) tapped:(UITapGestureRecognizer *) recognizer {
if (animating || self.disabled) {
return;
}
if (recognizer.state == UIGestureRecognizerStateRecognized) {
NSInteger newPage;
if ([recognizer locationInView:self].y < (self.bounds.size.height - self.bounds.origin.y) / 2) {
newPage = MAX(1, self.currentPage - 1);
} else {
newPage = MIN(self.currentPage + 1, numberOfPages);
}
[self setCurrentPage:newPage animated:YES];
}
}
- (void) panned:(UIPanGestureRecognizer *) recognizer {
if (animating) {
return;
}
static BOOL hasFailed;
static BOOL initialized;
static NSInteger oldPage;
float translation = [recognizer translationInView:self].y;
float progress = translation / self.bounds.size.height;
if (flipDirection == AFKPageFlipperDirectionTop) {
progress = MIN(progress, 0);
} else {
progress = MAX(progress, 0);
}
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
hasFailed = FALSE;
initialized = FALSE;
animating = NO;
setNextViewOnCompletion = NO;
break;
case UIGestureRecognizerStateChanged:
if (hasFailed) {
return;
}
if (!initialized) {
oldPage = self.currentPage;
if (translation > 0) {
if (self.currentPage > 1) {
[self doSetCurrentPage:self.currentPage - 1];
} else {
hasFailed = TRUE;
return;
}
} else {
if (self.currentPage < numberOfPages) {
[self doSetCurrentPage:self.currentPage + 1];
} else {
hasFailed = TRUE;
return;
}
}
hasFailed = NO;
initialized = TRUE;
setNextViewOnCompletion = NO;
[self initFlip];
}
[self setFlipProgress:fabs(progress) setDelegate:NO animate:NO];
break;
case UIGestureRecognizerStateFailed:
[self setFlipProgress:0.0 setDelegate:YES animate:YES];
currentPage = oldPage;
break;
case UIGestureRecognizerStateRecognized:
if (hasFailed) {
[self setFlipProgress:0.0 setDelegate:YES animate:YES];
currentPage = oldPage;
return;
}
if (fabs((translation + [recognizer velocityInView:self].y / 4) / self.bounds.size.height) > 0.5) {
setNextViewOnCompletion = YES;
[self setFlipProgress:1.0 setDelegate:YES animate:YES];
} else {
[self setFlipProgress:0.0 setDelegate:YES animate:YES];
currentPage = oldPage;
}
break;
default:
break;
}
}
#pragma mark -
#pragma mark Frame management
- (void) setFrame:(CGRect) value {
super.frame = value;
numberOfPages = [dataSource numberOfPagesForPageFlipper:self];
if (self.currentPage > numberOfPages) {
self.currentPage = numberOfPages;
}
}
#pragma mark -
#pragma mark Initialization and memory management
+ (Class) layerClass {
return [CATransformLayer class];
}
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
_tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
_panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panned:)];
[_tapRecognizer requireGestureRecognizerToFail:_panRecognizer];
[self addGestureRecognizer:_tapRecognizer];
[self addGestureRecognizer:_panRecognizer];
}
return self;
}
- (void)dealloc {
self.dataSource = Nil;
self.currentView = Nil;
self.nextView = Nil;
self.tapRecognizer = Nil;
self.panRecognizer = Nil;
[super dealloc];
}
#end
becoz of AFKPageFlipper, i am able to do the above code....credit goes to Mr. mtabini ( AFKPageFlipper author )

You could set a negative scale, like:
[theView setTransform:CGAffineTransformMakeScale(1, -1)];

Same as Pradeep, but with shadows, also ARC adapted:
AFKPageFlipper.h
//
// AFKPageFlipper.h
// AFKPageFlipper
//
// Created by Marco Tabini on 10-10-11.
// Copyright 2010 AFK Studio Partnership. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#class AFKPageFlipper;
#protocol AFKPageFlipperDataSource
- (NSInteger) numberOfPagesForPageFlipper:(AFKPageFlipper *) pageFlipper;
- (UIView *) viewForPage:(NSInteger) page inFlipper:(AFKPageFlipper *) pageFlipper;
#end
typedef enum {
AFKPageFlipperDirectionTop,
AFKPageFlipperDirectionBottom,
} AFKPageFlipperDirection;
#interface AFKPageFlipper : UIView {
NSObject <AFKPageFlipperDataSource> *dataSource;
NSInteger currentPage;
NSInteger numberOfPages;
// shadows
CALayer *frontLayerShadow;
CALayer *backLayerShadow;
CALayer *leftLayerShadow;
CALayer *rightLayerShadow;
// shadows
CALayer *backgroundAnimationLayer;
CALayer *flipAnimationLayer;
AFKPageFlipperDirection flipDirection;
float startFlipAngle;
float endFlipAngle;
float currentAngle;
BOOL setNextViewOnCompletion;
BOOL animating;
BOOL disabled;
}
#property (nonatomic,retain) NSObject <AFKPageFlipperDataSource> *dataSource;
#property (nonatomic,assign) NSInteger currentPage;
#property (nonatomic, retain) UITapGestureRecognizer *tapRecognizer;
#property (nonatomic, retain) UIPanGestureRecognizer *panRecognizer;
#property (nonatomic,assign) BOOL disabled;
- (void) setCurrentPage:(NSInteger) value animated:(BOOL) animated;
#end
AFKPageFlipper.m
//
// AFKPageFlipper.m
// AFKPageFlipper
//
// Created by Marco Tabini on 10-10-12.
// Copyright 2010 AFK Studio Partnership. All rights reserved.
//
#import "AFKPageFlipper.h"
#pragma mark -
#pragma mark UIView helpers
#interface UIView(Extended)
- (UIImage *) imageByRenderingView;
#end
#implementation UIView(Extended)
- (UIImage *) imageByRenderingView {
CGFloat oldAlpha = self.alpha;
self.alpha = 1;
UIGraphicsBeginImageContext(self.bounds.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.alpha = oldAlpha;
return resultingImage;
}
#end
#pragma mark -
#pragma mark Private interface
#interface AFKPageFlipper()
#property (nonatomic,retain) UIView *currentView;
#property (nonatomic,retain) UIView *nextView;
#end
#implementation AFKPageFlipper
#synthesize tapRecognizer = _tapRecognizer;
#synthesize panRecognizer = _panRecognizer;
#pragma mark -
#pragma mark Flip functionality
- (void) initFlip {
// Create screenshots of view
UIImage *currentImage = [self.currentView imageByRenderingView];
UIImage *newImage = [self.nextView imageByRenderingView];
// Hide existing views
self.currentView.alpha = 0;
self.nextView.alpha = 0;
// Create representational layers
CGRect rect = self.bounds;
rect.size.height /= 2;
backgroundAnimationLayer = [CALayer layer];
backgroundAnimationLayer.frame = self.bounds;
backgroundAnimationLayer.zPosition = -300000;
CALayer *topLayer = [CALayer layer];
topLayer.frame = rect;
topLayer.masksToBounds = YES;
topLayer.contentsGravity = kCAGravityBottom;
[backgroundAnimationLayer addSublayer:topLayer];
rect.origin.y = rect.size.height;
CALayer *bottomLayer = [CALayer layer];
bottomLayer.frame = rect;
bottomLayer.masksToBounds = YES;
bottomLayer.contentsGravity = kCAGravityTop;
[backgroundAnimationLayer addSublayer:bottomLayer];
if (flipDirection == AFKPageFlipperDirectionBottom) {
topLayer.contents = (id) [newImage CGImage];
bottomLayer.contents = (id) [currentImage CGImage];
} else {
topLayer.contents = (id) [currentImage CGImage];
bottomLayer.contents = (id) [newImage CGImage];
}
[self.layer addSublayer:backgroundAnimationLayer];
rect.origin.y = 0;
flipAnimationLayer = [CATransformLayer layer];
flipAnimationLayer.anchorPoint = CGPointMake(0.5, 1);
flipAnimationLayer.frame = rect;
[self.layer addSublayer:flipAnimationLayer];
CALayer *backLayer = [CALayer layer];
backLayer.frame = flipAnimationLayer.bounds;
backLayer.doubleSided = NO;
backLayer.masksToBounds = YES;
[flipAnimationLayer addSublayer:backLayer];
CALayer *frontLayer = [CALayer layer];
frontLayer.frame = flipAnimationLayer.bounds;
frontLayer.doubleSided = NO;
frontLayer.masksToBounds = YES;
frontLayer.transform = CATransform3DMakeRotation(M_PI, 1.0, 0, 0);
[flipAnimationLayer addSublayer:frontLayer];
// shadows
frontLayerShadow = [CALayer layer];
frontLayerShadow.frame = frontLayer.bounds;
frontLayerShadow.doubleSided = NO;
frontLayerShadow.masksToBounds = YES;
frontLayerShadow.opacity = 0;
frontLayerShadow.backgroundColor = [UIColor blackColor].CGColor;
[frontLayer addSublayer:frontLayerShadow];
backLayerShadow = [CALayer layer];
backLayerShadow.frame = backLayer.bounds;
backLayerShadow.doubleSided = NO;
backLayerShadow.masksToBounds = YES;
backLayerShadow.opacity = 0;
backLayerShadow.backgroundColor = [UIColor blackColor].CGColor;
[backLayer addSublayer:backLayerShadow];
leftLayerShadow = [CALayer layer];
leftLayerShadow.frame = topLayer.bounds;
leftLayerShadow.doubleSided = NO;
leftLayerShadow.masksToBounds = YES;
leftLayerShadow.opacity = 0.0;
leftLayerShadow.backgroundColor = [UIColor blackColor].CGColor;
[topLayer addSublayer:leftLayerShadow];
rightLayerShadow = [CALayer layer];
rightLayerShadow.frame = bottomLayer.bounds;
rightLayerShadow.doubleSided = NO;
rightLayerShadow.masksToBounds = YES;
rightLayerShadow.opacity = 0.0;
rightLayerShadow.backgroundColor = [UIColor blackColor].CGColor;
[bottomLayer addSublayer:rightLayerShadow];
// shadows
if (flipDirection == AFKPageFlipperDirectionBottom) {
backLayer.contents = (id) [currentImage CGImage];
backLayer.contentsGravity = kCAGravityBottom;
frontLayer.contents = (id) [newImage CGImage];
frontLayer.contentsGravity = kCAGravityTop;
CATransform3D transform = CATransform3DMakeRotation(1.1/M_PI, 1.0, 0.0, 0.0);
transform.m34 = 1.0f / 2500.0f;
flipAnimationLayer.transform = transform;
currentAngle = startFlipAngle = 0;
endFlipAngle = M_PI;
} else {
//down
backLayer.contents = (id) [newImage CGImage];
backLayer.contentsGravity = kCAGravityBottom;
frontLayer.contents = (id) [currentImage CGImage];
frontLayer.contentsGravity = kCAGravityTop;
CATransform3D transform = CATransform3DMakeRotation(M_PI/1.1, 1.0, 0.0, 0.0);
transform.m34 = 1.0f / 2500.0f;
flipAnimationLayer.transform = transform;
currentAngle = startFlipAngle = M_PI;
endFlipAngle = 0;
}
}
- (void) cleanupFlip {
[backgroundAnimationLayer removeFromSuperlayer];
[flipAnimationLayer removeFromSuperlayer];
backgroundAnimationLayer = Nil;
flipAnimationLayer = Nil;
animating = NO;
if (setNextViewOnCompletion) {
[self.currentView removeFromSuperview];
self.currentView = self.nextView;
self.nextView = Nil;
} else {
[self.nextView removeFromSuperview];
self.nextView = Nil;
}
self.currentView.alpha = 1;
}
- (void) setFlipProgress:(float) progress setDelegate:(BOOL) setDelegate animate:(BOOL) animate {
if (animate) {
animating = YES;
}
float newAngle = startFlipAngle + progress * (endFlipAngle - startFlipAngle);
float duration = animate ? 0.5 * fabs((newAngle - currentAngle) / (endFlipAngle - startFlipAngle)) : 0;
currentAngle = newAngle;
CATransform3D endTransform = CATransform3DIdentity;
endTransform.m34 = 1.0f / 2500.0f;
endTransform = CATransform3DRotate(endTransform, newAngle, 1.0, 0.0, 0.0);
[flipAnimationLayer removeAllAnimations];
// shadows
//NSLog(#"End flip angle: %.0f, \tstartflip: %.0f, \tprogress: %.2f\tduration: %.2f", endFlipAngle, startFlipAngle,progress, duration);
CGFloat newShadowOpacity = (0.5 - progress);
if(newShadowOpacity < 0) {
newShadowOpacity *= -1;
}
if (newShadowOpacity < 0.05) {
newShadowOpacity = 0;
}
// shadows
if (duration < 0.15) {
duration = 0.15;
}
[UIView animateWithDuration: duration delay: 0 options: UIViewAnimationOptionCurveLinear animations: ^(void) {
flipAnimationLayer.transform = endTransform;
if (endFlipAngle < startFlipAngle) {
if(progress < 0.5) {
rightLayerShadow.opacity = newShadowOpacity;
frontLayerShadow.opacity = (0.5 - newShadowOpacity)/2;
} else {
backLayerShadow.opacity = (0.5 - newShadowOpacity)/2;
leftLayerShadow.opacity = newShadowOpacity;
}
} else {
if(progress < 0.5) {
leftLayerShadow.opacity = newShadowOpacity;
backLayerShadow.opacity = (0.5 - newShadowOpacity)/2;
} else {
frontLayerShadow.opacity = (0.5 - newShadowOpacity)/2;
rightLayerShadow.opacity = newShadowOpacity;
}
}
// shadows
} completion: ^(BOOL completion) {
}];
if (setDelegate) {
[self performSelector:#selector(cleanupFlip) withObject:Nil afterDelay:duration];
}
}
- (void) flipPage {
[self setFlipProgress:1.0 setDelegate:YES animate:YES];
}
#pragma mark -
#pragma mark Animation management
- (void)animationDidStop:(NSString *) animationID finished:(NSNumber *) finished context:(void *) context {
[self cleanupFlip];
}
#pragma mark -
#pragma mark Properties
#synthesize currentPage;
- (BOOL) doSetCurrentPage:(NSInteger) value {
if (value == currentPage) {
return FALSE;
}
flipDirection = value < currentPage ? AFKPageFlipperDirectionBottom : AFKPageFlipperDirectionTop;
currentPage = value;
self.nextView = [self.dataSource viewForPage:value inFlipper:self];
[self addSubview:self.nextView];
return TRUE;
}
- (void) setCurrentPage:(NSInteger) value {
if (![self doSetCurrentPage:value]) {
return;
}
setNextViewOnCompletion = YES;
animating = YES;
self.nextView.alpha = 0;
[UIView beginAnimations:#"" context:Nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
self.nextView.alpha = 1;
[UIView commitAnimations];
}
- (void) setCurrentPage:(NSInteger) value animated:(BOOL) animated {
if (![self doSetCurrentPage:value]) {
return;
}
setNextViewOnCompletion = YES;
animating = YES;
if (animated) {
[self initFlip];
[self performSelector:#selector(flipPage) withObject:Nil afterDelay:0.091];
} else {
[self animationDidStop:Nil finished:[NSNumber numberWithBool:NO] context:Nil];
}
}
#synthesize dataSource;
- (void) setDataSource:(NSObject <AFKPageFlipperDataSource>*) value {
if (dataSource) {
dataSource = nil;
}
dataSource = value;
numberOfPages = [dataSource numberOfPagesForPageFlipper:self];
currentPage = 0;
self.currentPage = 1;
}
#synthesize disabled;
- (void) setDisabled:(BOOL) value {
disabled = value;
self.userInteractionEnabled = !value;
for (UIGestureRecognizer *recognizer in self.gestureRecognizers) {
recognizer.enabled = !value;
}
}
#pragma mark -
#pragma mark Touch management
- (void) tapped:(UITapGestureRecognizer *) recognizer {
if (animating || self.disabled) {
return;
}
if (recognizer.state == UIGestureRecognizerStateRecognized) {
NSInteger newPage;
if ([recognizer locationInView:self].y < (self.bounds.size.height - self.bounds.origin.y) / 2) {
newPage = MAX(1, self.currentPage - 1);
} else {
newPage = MIN(self.currentPage + 1, numberOfPages);
}
[self setCurrentPage:newPage animated:YES];
}
}
- (void) panned:(UIPanGestureRecognizer *) recognizer {
if (animating) {
return;
}
static BOOL hasFailed;
static BOOL initialized;
static NSInteger oldPage;
float translation = [recognizer translationInView:self].y;
float progress = translation / self.bounds.size.height;
if (flipDirection == AFKPageFlipperDirectionTop) {
progress = MIN(progress, 0);
} else {
progress = MAX(progress, 0);
}
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
hasFailed = FALSE;
initialized = FALSE;
animating = NO;
setNextViewOnCompletion = NO;
break;
case UIGestureRecognizerStateChanged:
if (hasFailed) {
return;
}
if (!initialized) {
oldPage = self.currentPage;
if (translation > 0) {
if (self.currentPage > 1) {
[self doSetCurrentPage:self.currentPage - 1];
} else {
hasFailed = TRUE;
return;
}
} else {
if (self.currentPage < numberOfPages) {
[self doSetCurrentPage:self.currentPage + 1];
} else {
hasFailed = TRUE;
return;
}
}
hasFailed = NO;
initialized = TRUE;
setNextViewOnCompletion = NO;
[self initFlip];
}
[self setFlipProgress:fabs(progress) setDelegate:NO animate:NO];
break;
case UIGestureRecognizerStateFailed:
[self setFlipProgress:0.0 setDelegate:YES animate:YES];
currentPage = oldPage;
break;
case UIGestureRecognizerStateRecognized:
if (hasFailed) {
[self setFlipProgress:0.0 setDelegate:YES animate:YES];
currentPage = oldPage;
return;
}
if (fabs((translation + [recognizer velocityInView:self].y / 4) / self.bounds.size.height) > 0.5) {
setNextViewOnCompletion = YES;
[self setFlipProgress:1.0 setDelegate:YES animate:YES];
} else {
[self setFlipProgress:0.0 setDelegate:YES animate:YES];
currentPage = oldPage;
}
break;
default:
break;
}
}
#pragma mark -
#pragma mark Frame management
/*
- (void) setFrame:(CGRect) value {
super.frame = value;
numberOfPages = [dataSource numberOfPagesForPageFlipper:self];
if (self.currentPage > numberOfPages) {
self.currentPage = numberOfPages;
}
}*/
#pragma mark -
#pragma mark Initialization and memory management
+ (Class) layerClass {
return [CATransformLayer class];
}
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self initRecognizers];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder: aDecoder])) {
[self initRecognizers];
}
return self;
}
- (void) initRecognizers {
_tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
_panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panned:)];
[_tapRecognizer requireGestureRecognizerToFail:_panRecognizer];
[self addGestureRecognizer:_tapRecognizer];
[self addGestureRecognizer:_panRecognizer];
}
#end

Related

UIScrollView - when is contentSize set

I have a UIViewController and it's view hierarchy looks like this:
UIView
UIScrollView
UIImageView
I have code that positions the image view in the middle of the scroll view's frame, like so:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
[self recenterContent:scrollView];
}
- (void)recenterContent:(UIScrollView *)scrollView {
//this centers the content when it is smaller than the scrollView's bounds
CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}
This works fine when zooming the content, but when the view controller first loads it does not center. This is because the scrollView.contentSize is always 0. So my question is - when should I call this method after the scrollView.contentSize is set? When does that get set?
I have tried in viewDidLayoutSubviews, and the bounds of the scroll view is set then, but not the content size. Is there some method that I can use where the scroll view will be guaranteed to have the content size set?
Or is there a better way to keep the image centered when it is smaller than the scroll view? What I am trying to accomplish is to have it so the image view is not at the top of the scroll view and what I am using works, except when the scroll view's content size is not set. But if there is a better way of doing this without having to adjust the contentInset, I would be fine with that too.
Update
Here is what I have currently.
It is almost working, but no matter what I try, I cannot get it to look correct when the view loads. The way it works now is that it starts out off-center because when it calls the recenterContent method, before the view is displayed the content size of the scroll view is CGSizeZero, so the calculations are wrong. But if I try to recenter the content after the view has been displayed, then there is a visible delay before it gets centered.
I am just confused as to when the contentSize of the scroll view is set if I am using AutoLayout constraints to specify the size.
Here is my code. Can anyone see anything wrong with it?
#interface MyImageViewController ()
#property (strong, nonatomic) UIScrollView *scrollView;
#property (strong, nonatomic) UIImageView *imageView;
#property (assign, nonatomic) BOOL needsZoomScale;
#end
#implementation MyImageViewController
- (void)loadView {
self.view = [[UIView alloc] init];
[self.view addSubview:self.scrollView];
[self.scrollView addSubview:self.imageView];
self.needsZoomScale = YES;
[NSLayoutConstraint activateConstraints:#[
[self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.scrollView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
[self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[self.imageView.leadingAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.leadingAnchor],
[self.imageView.topAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.topAnchor],
[self.imageView.trailingAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.trailingAnchor],
[self.imageView.bottomAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.bottomAnchor]
]];
}
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTapZoom:)];
doubleTapGesture.numberOfTapsRequired = 2;
[self.imageView addGestureRecognizer:doubleTapGesture];
}
- (CGRect)zoomRectForScrollView:(UIScrollView *)scrollView withScale:(CGFloat)scale withCenter:(CGPoint)center {
CGRect zoomRect;
//the zoom rect is in the content view's coordinates. At a zoom scale of 1.0, the zoom rect would be the size
//of the scroll view's bounds. As the zoom scale decreases, so more content is visible, the size of the rect
//grows.
zoomRect.size.width = scrollView.frame.size.width / scale;
zoomRect.size.height = scrollView.frame.size.height / scale;
//choose an origin so as to get the right center
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
return zoomRect;
}
- (void)doubleTapZoom:(UITapGestureRecognizer *)sender {
UIView *tappedView = sender.view;
CGPoint tappedPoint = [sender locationInView:tappedView];
if (tappedPoint.x <= 0) {
tappedPoint.x = 1;
}
if (tappedPoint.y <= 0) {
tappedPoint.y = 1;
}
if (tappedPoint.x >= tappedView.bounds.size.width) {
tappedPoint.x = tappedView.bounds.size.width - 1;
}
if (tappedPoint.y >= tappedView.bounds.size.height) {
tappedPoint.y = tappedView.bounds.size.height - 1;
}
CGFloat zoomScale;
if (self.scrollView.zoomScale < 1) {
zoomScale = 1;
} else if (self.scrollView.zoomScale < self.scrollView.maximumZoomScale) {
zoomScale = self.scrollView.maximumZoomScale;
} else {
zoomScale = self.scrollView.minimumZoomScale;
}
CGRect zoomRect = [self zoomRectForScrollView:self.scrollView withScale:zoomScale withCenter:tappedPoint];
[self.scrollView zoomToRect:zoomRect animated:YES];
}
- (UIScrollView *)scrollView {
if (!self->_scrollView) {
self->_scrollView = [[UIScrollView alloc] init];
self->_scrollView.translatesAutoresizingMaskIntoConstraints = NO;
self->_scrollView.minimumZoomScale = 0.1f;
self->_scrollView.maximumZoomScale = 4.0f;
self->_scrollView.bounces = YES;
self->_scrollView.bouncesZoom = YES;
self->_scrollView.delegate = self;
self->_scrollView.backgroundColor = [UIColor blackColor];
}
return self->_scrollView;
}
- (UIImageView *)imageView {
if (!self->_imageView) {
self->_imageView = [[UIImageView alloc] init];
self->_imageView.translatesAutoresizingMaskIntoConstraints = NO;
self->_imageView.userInteractionEnabled = YES;
}
return self->_imageView;
}
- (UIImage *)image {
return self.imageView.image;
}
- (void)setImage:(UIImage *)image {
self.imageView.image = image;
self.needsZoomScale = YES;
[self updateZoomScale];
}
- (void)updateZoomScale {
if (self.needsZoomScale && self.image) {
CGSize size = self.view.bounds.size;
if (size.width == 0.0f || size.height == 0.0f) {
return;
}
UIImage *image = self.image;
CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
if (imageSize.width > 0 && imageSize.height > 0) {
CGFloat widthScale = size.width / imageSize.width;
CGFloat heightScale = size.height / imageSize.height;
CGFloat minScale = MIN(widthScale, heightScale);
self.scrollView.minimumZoomScale = minScale;
self.scrollView.zoomScale = minScale;
self.needsZoomScale = NO;
}
}
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self updateZoomScale];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self recenterContent:self.scrollView];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self recenterContent:self.scrollView];
}
#pragma mark - UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
[self recenterContent:scrollView];
}
- (void)recenterContent:(UIScrollView *)scrollView {
//this centers the content when it is smaller than the scrollView's bounds
CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}
#end
The problem is that a UIImageView has an intrinsic content size of 0,0 -- so your code is initially putting the a 0x0 image view at the center of the scroll view.
I've made a few changes to the code you posted... see comments (I "wrapped" the changes in
// ---------------------------------
comment lines:
#interface MyImageViewController : UIViewController <UIScrollViewDelegate>
#end
#interface MyImageViewController ()
#property (strong, nonatomic) UIScrollView *scrollView;
#property (strong, nonatomic) UIImageView *imageView;
#property (assign, nonatomic) BOOL needsZoomScale;
#end
#implementation MyImageViewController
- (void)loadView {
self.view = [[UIView alloc] init];
[self.view addSubview:self.scrollView];
[self.scrollView addSubview:self.imageView];
self.needsZoomScale = YES;
// ---------------------------------
// respect safe area
UILayoutGuide *g = [self.view safeAreaLayoutGuide];
// saves on a little typing
UILayoutGuide *sg = [self.scrollView contentLayoutGuide];
// ---------------------------------
[NSLayoutConstraint activateConstraints:#[
[self.scrollView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor],
[self.scrollView.topAnchor constraintEqualToAnchor:g.topAnchor],
[self.scrollView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor],
[self.scrollView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor],
[self.imageView.leadingAnchor constraintEqualToAnchor:sg.leadingAnchor],
[self.imageView.topAnchor constraintEqualToAnchor:sg.topAnchor],
[self.imageView.trailingAnchor constraintEqualToAnchor:sg.trailingAnchor],
[self.imageView.bottomAnchor constraintEqualToAnchor:sg.bottomAnchor]
]];
}
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTapZoom:)];
doubleTapGesture.numberOfTapsRequired = 2;
[self.imageView addGestureRecognizer:doubleTapGesture];
}
- (CGRect)zoomRectForScrollView:(UIScrollView *)scrollView withScale:(CGFloat)scale withCenter:(CGPoint)center {
CGRect zoomRect;
//the zoom rect is in the content view's coordinates. At a zoom scale of 1.0, the zoom rect would be the size
//of the scroll view's bounds. As the zoom scale decreases, so more content is visible, the size of the rect
//grows.
zoomRect.size.width = scrollView.frame.size.width / scale;
zoomRect.size.height = scrollView.frame.size.height / scale;
//choose an origin so as to get the right center
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
return zoomRect;
}
- (void)doubleTapZoom:(UITapGestureRecognizer *)sender {
UIView *tappedView = sender.view;
CGPoint tappedPoint = [sender locationInView:tappedView];
if (tappedPoint.x <= 0) {
tappedPoint.x = 1;
}
if (tappedPoint.y <= 0) {
tappedPoint.y = 1;
}
if (tappedPoint.x >= tappedView.bounds.size.width) {
tappedPoint.x = tappedView.bounds.size.width - 1;
}
if (tappedPoint.y >= tappedView.bounds.size.height) {
tappedPoint.y = tappedView.bounds.size.height - 1;
}
CGFloat zoomScale;
if (self.scrollView.zoomScale < 1) {
zoomScale = 1;
} else if (self.scrollView.zoomScale < self.scrollView.maximumZoomScale) {
zoomScale = self.scrollView.maximumZoomScale;
} else {
zoomScale = self.scrollView.minimumZoomScale;
}
CGRect zoomRect = [self zoomRectForScrollView:self.scrollView withScale:zoomScale withCenter:tappedPoint];
[self.scrollView zoomToRect:zoomRect animated:YES];
}
- (UIScrollView *)scrollView {
if (!self->_scrollView) {
self->_scrollView = [[UIScrollView alloc] init];
self->_scrollView.translatesAutoresizingMaskIntoConstraints = NO;
self->_scrollView.minimumZoomScale = 0.1f;
self->_scrollView.maximumZoomScale = 4.0f;
self->_scrollView.bounces = YES;
self->_scrollView.bouncesZoom = YES;
self->_scrollView.delegate = self;
self->_scrollView.backgroundColor = [UIColor blackColor];
}
return self->_scrollView;
}
- (UIImageView *)imageView {
if (!self->_imageView) {
self->_imageView = [[UIImageView alloc] init];
self->_imageView.translatesAutoresizingMaskIntoConstraints = NO;
self->_imageView.userInteractionEnabled = YES;
}
return self->_imageView;
}
- (UIImage *)image {
return self.imageView.image;
}
- (void)setImage:(UIImage *)image {
self.imageView.image = image;
// ---------------------------------
// set the frame here
self.imageView.frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
// ---------------------------------
// not needed ... unless maybe changing the image while view is showing?
//self.needsZoomScale = YES;
//[self updateZoomScale];
}
- (void)updateZoomScale {
if (self.needsZoomScale && self.image) {
CGSize size = self.view.bounds.size;
if (size.width == 0.0f || size.height == 0.0f) {
return;
}
UIImage *image = self.image;
CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
if (imageSize.width > 0 && imageSize.height > 0) {
CGFloat widthScale = size.width / imageSize.width;
CGFloat heightScale = size.height / imageSize.height;
CGFloat minScale = MIN(widthScale, heightScale);
self.scrollView.minimumZoomScale = minScale;
self.scrollView.zoomScale = minScale;
self.needsZoomScale = NO;
}
}
}
// ---------------------------------
// Don't need this
//- (void)viewWillLayoutSubviews {
// [super viewWillLayoutSubviews];
// [self updateZoomScale];
//}
// ---------------------------------
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// ---------------------------------
// update zoom scale here
[self updateZoomScale];
// ---------------------------------
[self recenterContent:self.scrollView];
}
// ---------------------------------
// Don't need this
//- (void)viewDidAppear:(BOOL)animated {
// [super viewDidAppear:animated];
// [self recenterContent:self.scrollView];
//}
// ---------------------------------
#pragma mark - UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
[self recenterContent:scrollView];
}
- (void)recenterContent:(UIScrollView *)scrollView {
//this centers the content when it is smaller than the scrollView's bounds
CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}
#end
and here's how I call it:
MyImageViewController *vc = [MyImageViewController new];
UIImage *img = [UIImage imageNamed:#"bkg"];
if (nil == img) {
NSLog(#"Could not load image!!!!");
return;
}
[vc setImage:img];
[self.navigationController pushViewController:vc animated:YES];

Not allow slide from 1% to 100% and vice versa in Circular Slider iOS

I'm using this class to create a circular slider but i have a problem,how to not allow when user slide from 1% to 100% and vice versa? Please help me to fix this issue. Thanks in advance.
Here is code:
#interface SLCircularSlider()
#property (nonatomic) CGPoint thumbCenterPoint;
#pragma mark - Init and Setup methods
- (void)setup;
#pragma mark - Thumb management methods
- (BOOL)isPointInThumb:(CGPoint)point;
#pragma mark - Drawing methods
- (CGFloat)sliderRadius;
- (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext:(CGContextRef)context;
- (CGPoint)drawCircularTrack:(float)track atPoint:(CGPoint)point withRadius:(CGFloat)radius inContext:(CGContextRef)context;
- (CGPoint)drawPieTrack:(float)track atPoint:(CGPoint)point withRadius:(CGFloat)radius inContext:(CGContextRef)context;
#end
#pragma mark -
#implementation SLCircularSlider
#synthesize value = _value;
- (void)setValue:(float)value {
if (value != _value) {
if (value > self.maximumValue) { value = self.maximumValue; }
if (value < self.minimumValue) { value = self.minimumValue; }
_value = value;
[self setNeedsDisplay];
if (self.isContinuous) {
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
}
#synthesize minimumValue = _minimumValue;
- (void)setMinimumValue:(float)minimumValue {
if (minimumValue != _minimumValue) {
_minimumValue = minimumValue;
if (self.maximumValue < self.minimumValue) { self.maximumValue = self.minimumValue; }
if (self.value < self.minimumValue) { self.value = self.minimumValue; }
}
}
#synthesize maximumValue = _maximumValue;
- (void)setMaximumValue:(float)maximumValue {
if (maximumValue != _maximumValue) {
_maximumValue = maximumValue;
if (self.minimumValue > self.maximumValue) { self.minimumValue = self.maximumValue; }
if (self.value > self.maximumValue) { self.value = self.maximumValue; }
}
}
#synthesize minimumTrackTintColor = _minimumTrackTintColor;
- (void)setMinimumTrackTintColor:(UIColor *)minimumTrackTintColor {
if (![minimumTrackTintColor isEqual:_minimumTrackTintColor]) {
_minimumTrackTintColor = minimumTrackTintColor;
[self setNeedsDisplay];
}
}
#synthesize maximumTrackTintColor = _maximumTrackTintColor;
- (void)setMaximumTrackTintColor:(UIColor *)maximumTrackTintColor {
if (![maximumTrackTintColor isEqual:_maximumTrackTintColor]) {
_maximumTrackTintColor = maximumTrackTintColor;
[self setNeedsDisplay];
}
}
#synthesize thumbTintColor = _thumbTintColor;
- (void)setThumbTintColor:(UIColor *)thumbTintColor {
if (![thumbTintColor isEqual:_thumbTintColor]) {
_thumbTintColor = thumbTintColor;
[self setNeedsDisplay];
}
}
#synthesize continuous = _continuous;
#synthesize sliderStyle = _sliderStyle;
- (void)setSliderStyle:(UICircularSliderStyle)sliderStyle {
if (sliderStyle != _sliderStyle) {
_sliderStyle = sliderStyle;
[self setNeedsDisplay];
}
}
#synthesize thumbCenterPoint = _thumbCenterPoint;
/** #name Init and Setup methods */
#pragma mark - Init and Setup methods
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)awakeFromNib {
[self setup];
}
- (void)setup {
self.value = 0.0;
self.minimumValue = 0.0;
self.maximumValue = 1.0;
/*self.minimumTrackTintColor = [UIColor blueColor];
self.maximumTrackTintColor = [UIColor whiteColor];
self.thumbTintColor = [UIColor darkGrayColor];*/
self.minimumTrackTintColor = [UIColor clearColor];
self.maximumTrackTintColor = [UIColor clearColor];
self.thumbTintColor = [UIColor clearColor];
self.continuous = YES;
self.thumbCenterPoint = CGPointZero;
/**
* This tapGesture isn't used yet but will allow to jump to a specific location in the circle
*/
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGestureHappened:)];
[self addGestureRecognizer:tapGestureRecognizer];
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGestureHappened:)];
panGestureRecognizer.maximumNumberOfTouches = panGestureRecognizer.minimumNumberOfTouches;
[self addGestureRecognizer:panGestureRecognizer];
}
/** #name Drawing methods */
#pragma mark - Drawing methods
#define kLineWidth 5.0
#define kThumbRadius 12.0
- (CGFloat)sliderRadius {
CGFloat radius = MIN(self.bounds.size.width/2, self.bounds.size.height/2);
radius -= MAX(kLineWidth, kThumbRadius);
return radius;
}
- (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext:(CGContextRef)context {
UIGraphicsPushContext(context);
CGContextBeginPath(context);
CGContextMoveToPoint(context, sliderButtonCenterPoint.x, sliderButtonCenterPoint.y);
CGContextAddArc(context, sliderButtonCenterPoint.x, sliderButtonCenterPoint.y, kThumbRadius, 0.0, 2*M_PI, NO);
CGContextFillPath(context);
UIGraphicsPopContext();
}
- (CGPoint)drawCircularTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius inContext:(CGContextRef)context {
UIGraphicsPushContext(context);
CGContextBeginPath(context);
float angleFromTrack = translateValueFromSourceIntervalToDestinationInterval(track, self.minimumValue, self.maximumValue, 0, 2*M_PI);
CGFloat startAngle = -M_PI_2;
CGFloat endAngle = startAngle + angleFromTrack;
CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, NO);
CGPoint arcEndPoint = CGContextGetPathCurrentPoint(context);
CGContextStrokePath(context);
UIGraphicsPopContext();
return arcEndPoint;
}
- (CGPoint)drawPieTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius inContext:(CGContextRef)context {
UIGraphicsPushContext(context);
float angleFromTrack = translateValueFromSourceIntervalToDestinationInterval(track, self.minimumValue, self.maximumValue, 0, 2*M_PI);
CGFloat startAngle = -M_PI_2;
CGFloat endAngle = startAngle + angleFromTrack;
CGContextMoveToPoint(context, center.x, center.y);
CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, NO);
CGPoint arcEndPoint = CGContextGetPathCurrentPoint(context);
CGContextClosePath(context);
CGContextFillPath(context);
UIGraphicsPopContext();
return arcEndPoint;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint middlePoint;
middlePoint.x = self.bounds.origin.x + self.bounds.size.width/2;
middlePoint.y = self.bounds.origin.y + self.bounds.size.height/2;
CGContextSetLineWidth(context, kLineWidth);
CGFloat radius = [self sliderRadius];
switch (self.sliderStyle) {
case UICircularSliderStylePie:
[self.maximumTrackTintColor setFill];
[self drawPieTrack:self.maximumValue atPoint:middlePoint withRadius:radius inContext:context];
[self.minimumTrackTintColor setStroke];
[self drawCircularTrack:self.maximumValue atPoint:middlePoint withRadius:radius inContext:context];
[self.minimumTrackTintColor setFill];
self.thumbCenterPoint = [self drawPieTrack:self.value atPoint:middlePoint withRadius:radius inContext:context];
break;
case UICircularSliderStyleCircle:
default:
[self.maximumTrackTintColor setStroke];
[self drawCircularTrack:self.maximumValue atPoint:middlePoint withRadius:radius inContext:context];
[self.minimumTrackTintColor setStroke];
self.thumbCenterPoint = [self drawCircularTrack:self.value atPoint:middlePoint withRadius:radius inContext:context];
break;
}
[self.thumbTintColor setFill];
[self drawThumbAtPoint:self.thumbCenterPoint inContext:context];
}
/** #name Thumb management methods */
#pragma mark - Thumb management methods
- (BOOL)isPointInThumb:(CGPoint)point {
CGRect thumbTouchRect = CGRectMake(self.thumbCenterPoint.x - kThumbRadius, self.thumbCenterPoint.y - kThumbRadius, kThumbRadius*2, kThumbRadius*2);
return CGRectContainsPoint(thumbTouchRect, point);
}
/** #name UIGestureRecognizer management methods */
#pragma mark - UIGestureRecognizer management methods
- (void)panGestureHappened:(UIPanGestureRecognizer *)panGestureRecognizer {
CGPoint tapLocation = [panGestureRecognizer locationInView:self];
/* UILabel* percentlbl =(UILabel*) [self.superview viewWithTag:10];
NSLog(#"percentlbl frame %f",percentlbl.frame.origin.y
);
if (CGRectContainsPoint(percentlbl.frame, tapLocation)) {
NSLog(#"Tapped label");
}*/
switch (panGestureRecognizer.state) {
case UIGestureRecognizerStateChanged: {
CGFloat radius = [self sliderRadius];
CGPoint sliderCenter = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
CGPoint sliderStartPoint = CGPointMake(sliderCenter.x, sliderCenter.y - radius);
CGFloat angle = angleBetweenThreePoints(sliderCenter, sliderStartPoint, tapLocation);
if (angle < 0) {
angle = -angle;
}
else {
angle = 2*M_PI - angle;
}
self.value = translateValueFromSourceIntervalToDestinationInterval(angle, 0, 2*M_PI, self.minimumValue, self.maximumValue);
break;
}
case UIGestureRecognizerStateEnded:
if (!self.isContinuous) {
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
if ([self isPointInThumb:tapLocation]) {
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
}
else {
[self sendActionsForControlEvents:UIControlEventTouchUpOutside];
}
break;
default:
break;
}
}
- (void)tapGestureHappened:(UITapGestureRecognizer *)tapGestureRecognizer {
if (tapGestureRecognizer.state == UIGestureRecognizerStateEnded) {
CGPoint tapLocation = [tapGestureRecognizer locationInView:self];
if ([self isPointInThumb:tapLocation]) {
}
else {
}
}
}
/** #name Touches Methods */
#pragma mark - Touches Methods
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self];
if ([self isPointInThumb:touchLocation]) {
[self sendActionsForControlEvents:UIControlEventTouchDown];
}
}
#end
/** #name Utility Functions */
#pragma mark - Utility Functions
float translateValueFromSourceIntervalToDestinationInterval(float sourceValue, float sourceIntervalMinimum, float sourceIntervalMaximum, float destinationIntervalMinimum, float destinationIntervalMaximum) {
float a, b, destinationValue;
a = (destinationIntervalMaximum - destinationIntervalMinimum) / (sourceIntervalMaximum - sourceIntervalMinimum);
b = destinationIntervalMaximum - a*sourceIntervalMaximum;
destinationValue = a*sourceValue + b;
return destinationValue;
}
CGFloat angleBetweenThreePoints(CGPoint centerPoint, CGPoint p1, CGPoint p2) {
CGPoint v1 = CGPointMake(p1.x - centerPoint.x, p1.y - centerPoint.y);
CGPoint v2 = CGPointMake(p2.x - centerPoint.x, p2.y - centerPoint.y);
CGFloat angle = atan2f(v2.x*v1.y - v1.x*v2.y, v1.x*v2.x + v1.y*v2.y);
return angle;
}
What you need to do is add a stage in your gesture recognition where you determine if the user is increasing or decreasing the counter. If decreasing, do not respond to decrease changes once the value drops below zero (probable you want to be able to get to zero, not stop at one despite what the question says?). If increasing, don't respond to increase changes once value gets to 100%. You need to ensure of course that while at the maximum you continue to be responsive if the user changes to decrease the value, and likewise to increasing when at zero.

Fill in the translucent color insider the circular slider

I would like to implement the custom circular slider for IOS. I refer to the EFCircularSlider to create my own one. When it comes to the customization such as filling in the translucent color insider the circular slider , i have found that this line is not working. Would you please tell me are there any other alternatives?
CGContextSetFillColorWithColor(ctx, [UIColor greenColor].CGColor );
The below is my code (EFCircularSlider.m)
#import "EFCircularSlider.h"
#import <QuartzCore/QuartzCore.h>
#import <CoreImage/CoreImage.h>
#define kDefaultFontSize 14.0f;
#define ToRad(deg) ( (M_PI * (deg)) / 180.0 )
#define ToDeg(rad) ( (180.0 * (rad)) / M_PI )
#define SQR(x) ( (x) * (x) )
#interface EFCircularSlider (private)
#property (readonly, nonatomic) CGFloat radius;
#end
#implementation EFCircularSlider {
int angle;
int fixedAngle;
NSMutableDictionary* labelsWithPercents;
NSArray* labelsEvenSpacing;
}
- (void)defaults {
// Defaults
_maximumValue = 100.0f;
_minimumValue = 0.0f;
_currentValue = 0.0f;
_lineWidth = 5;
_lineRadiusDisplacement = 0;
_unfilledColor = [UIColor lightGrayColor];
_filledColor = [UIColor blueColor];
_handleColor = _filledColor;
_labelFont = [UIFont systemFontOfSize:10.0f];
_snapToLabels = NO;
_handleType = EFSemiTransparentWhiteCircle;
_labelColor = [UIColor redColor];
_labelDisplacement = 2;
self.backgroundColor = [UIColor clearColor];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self defaults];
[self setFrame:frame];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self=[super initWithCoder:aDecoder])){
[self defaults];
}
return self;
}
#pragma mark - Setter/Getter
- (void)setFrame:(CGRect)frame {
[super setFrame:frame];
angle = [self angleFromValue];
}
- (CGFloat)radius {
//radius = self.frame.size.height/2 - [self circleDiameter]/2;
return self.frame.size.height/2 - _lineWidth/2 - ([self circleDiameter]-_lineWidth) - _lineRadiusDisplacement;
}
- (void)setCurrentValue:(float)currentValue {
_currentValue=currentValue;
if(_currentValue>_maximumValue) _currentValue=_maximumValue;
else if(_currentValue<_minimumValue) _currentValue=_minimumValue;
angle = [self angleFromValue];
[self setNeedsLayout];
[self setNeedsDisplay];
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
#pragma mark - drawing methods
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef ctx = UIGraphicsGetCurrentContext();
//Draw the unfilled circle
//CGContextAddArc(ctx, self.frame.size.width/2, self.frame.size.height/2, self.radius, 0, M_PI *2, 0);
CGContextAddArc(ctx, self.frame.size.width/2, self.frame.size.height/2, self.radius, 0, M_PI *2, 0);
[_unfilledColor setStroke];
CGContextSetLineWidth(ctx, _lineWidth);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
//Draw the filled circle
if((_handleType == EFDoubleCircleWithClosedCenter || _handleType == EFDoubleCircleWithOpenCenter) && fixedAngle > 5) {
CGContextAddArc(ctx, self.frame.size.width/2 , self.frame.size.height/2, self.radius, 3*M_PI/2, 3*M_PI/2-ToRad(angle+3), 0);
} else {
CGContextAddArc(ctx, self.frame.size.width/2 , self.frame.size.height/2, self.radius, 3*M_PI/2, 3*M_PI/2-ToRad(angle), 0);
}
[_filledColor setStroke];
CGContextSetLineWidth(ctx, _lineWidth);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
UIView *colourView = [[UIView alloc] initWithFrame:rect];
colourView.opaque = NO;
colourView.alpha = .7f;
//colourView.backgroundColor = [UIColor colorWithRed:0.13f green:0.14f blue:0.15f alpha:1.00f];
//CGContextSetStrokeColorWithColor(ctx, [UIColor greenColor].CGColor);
CGContextSetFillColorWithColor(ctx, [UIColor greenColor].CGColor );
// CGContextFillRect(ctx, (CGRect){ {0,0}, colourView.size} );
//Add the labels (if necessary)
if(labelsEvenSpacing != nil) {
[self drawLabels:ctx];
}
//The draggable part
[self drawHandle:ctx];
}
-(void) drawHandle:(CGContextRef)ctx{
CGContextSaveGState(ctx);
CGPoint handleCenter = [self pointFromAngle: angle];
if(_handleType == EFSemiTransparentWhiteCircle) {
[[UIColor colorWithWhite:0.3 alpha:0.7] set];
CGContextFillEllipseInRect(ctx, CGRectMake(handleCenter.x, handleCenter.y, _lineWidth, _lineWidth));
} else if(_handleType == EFSemiTransparentBlackCircle) {
[[UIColor colorWithWhite:0.0 alpha:0.7] set];
CGContextFillEllipseInRect(ctx, CGRectMake(handleCenter.x, handleCenter.y, _lineWidth, _lineWidth));
} else if(_handleType == EFDoubleCircleWithClosedCenter) {
[_handleColor set];
CGContextAddArc(ctx, handleCenter.x + (_lineWidth)/2, handleCenter.y + (_lineWidth)/2, _lineWidth, 0, M_PI *2, 0);
CGContextSetLineWidth(ctx, 7);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
CGContextFillEllipseInRect(ctx, CGRectMake(handleCenter.x, handleCenter.y, _lineWidth-1, _lineWidth-1));
} else if(_handleType == EFDoubleCircleWithOpenCenter) {
[_handleColor set];
CGContextAddArc(ctx, handleCenter.x + (_lineWidth)/2, handleCenter.y + (_lineWidth)/2, _lineWidth/2 + 5, 0, M_PI *2, 0);
CGContextSetLineWidth(ctx, 4);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
CGContextAddArc(ctx, handleCenter.x + _lineWidth/2, handleCenter.y + _lineWidth/2, _lineWidth/2, 0, M_PI *2, 0);
CGContextSetLineWidth(ctx, 2);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
} else if(_handleType == EFBigCircle) {
[_handleColor set];
CGContextFillEllipseInRect(ctx, CGRectMake(handleCenter.x-2.5, handleCenter.y-2.5, _lineWidth+5, _lineWidth+5));
}
CGContextRestoreGState(ctx);
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
CGPoint p1 = [self centerPoint];
CGPoint p2 = point;
CGFloat xDist = (p2.x - p1.x);
CGFloat yDist = (p2.y - p1.y);
double distance = sqrt((xDist * xDist) + (yDist * yDist));
return distance < self.radius + 11;
}
-(void) drawLabels:(CGContextRef)ctx {
if(labelsEvenSpacing == nil || [labelsEvenSpacing count] == 0) {
return;
} else {
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0
NSDictionary *attributes = #{ NSFontAttributeName: _labelFont,
NSForegroundColorAttributeName: _labelColor
};
#endif
CGFloat fontSize = ceilf(_labelFont.pointSize);
NSInteger distanceToMove = -[self circleDiameter]/2 - fontSize/2 - _labelDisplacement;
for (int i=0; i<[labelsEvenSpacing count]; i++)
{
NSString *label = [labelsEvenSpacing objectAtIndex:[labelsEvenSpacing count] - i - 1];
CGFloat percentageAlongCircle = i/(float)[labelsEvenSpacing count];
CGFloat degreesForLabel = percentageAlongCircle * 360;
CGSize labelSize=CGSizeMake([self widthOfString:label withFont:_labelFont], [self heightOfString:label withFont:_labelFont]);
CGPoint closestPointOnCircleToLabel = [self pointFromAngle:degreesForLabel withObjectSize:labelSize];
CGRect labelLocation = CGRectMake(closestPointOnCircleToLabel.x, closestPointOnCircleToLabel.y, labelSize.width, labelSize.height);
CGPoint centerPoint = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
float radiansTowardsCenter = ToRad(AngleFromNorth(centerPoint, closestPointOnCircleToLabel, NO));
labelLocation.origin.x = (labelLocation.origin.x + distanceToMove * cos(radiansTowardsCenter));
labelLocation.origin.y = (labelLocation.origin.y + distanceToMove * sin(radiansTowardsCenter));
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0
[label drawInRect:labelLocation withAttributes:attributes];
#else
[_labelColor setFill];
[label drawInRect:labelLocation withFont:_labelFont];
#endif
}
}
}
#pragma mark - UIControl functions
-(BOOL) beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
[super beginTrackingWithTouch:touch withEvent:event];
return YES;
}
-(BOOL) continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
[super continueTrackingWithTouch:touch withEvent:event];
CGPoint lastPoint = [touch locationInView:self];
[self moveHandle:lastPoint];
[self sendActionsForControlEvents:UIControlEventValueChanged];
return YES;
}
-(void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
[super endTrackingWithTouch:touch withEvent:event];
if(_snapToLabels && labelsEvenSpacing != nil) {
CGFloat newAngle=0;
float minDist = 360;
for (int i=0; i<[labelsEvenSpacing count]; i++) {
CGFloat percentageAlongCircle = i/(float)[labelsEvenSpacing count];
CGFloat degreesForLabel = percentageAlongCircle * 360;
if(abs(fixedAngle - degreesForLabel) < minDist) {
newAngle=degreesForLabel ? 360 - degreesForLabel : 0;
minDist = abs(fixedAngle - degreesForLabel);
}
}
angle = newAngle;
_currentValue = [self valueFromAngle];
[self setNeedsDisplay];
}
}
-(void)moveHandle:(CGPoint)point {
CGPoint centerPoint;
centerPoint = [self centerPoint];
int currentAngle = floor(AngleFromNorth(centerPoint, point, NO));
angle = 360 - 90 - currentAngle;
_currentValue = [self valueFromAngle];
[self setNeedsDisplay];
}
- (CGPoint)centerPoint {
return CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
}
#pragma mark - helper functions
-(CGPoint)pointFromAngle:(int)angleInt{
//Define the Circle center
CGPoint centerPoint = CGPointMake(self.frame.size.width/2 - _lineWidth/2, self.frame.size.height/2 - _lineWidth/2);
//Define The point position on the circumference
CGPoint result;
result.y = round(centerPoint.y + self.radius * sin(ToRad(-angleInt-90))) ;
result.x = round(centerPoint.x + self.radius * cos(ToRad(-angleInt-90)));
return result;
}
-(CGPoint)pointFromAngle:(int)angleInt withObjectSize:(CGSize)size{
//Define the Circle center
CGPoint centerPoint = CGPointMake(self.frame.size.width/2 - size.width/2, self.frame.size.height/2 - size.height/2);
//Define The point position on the circumference
CGPoint result;
result.y = round(centerPoint.y + self.radius * sin(ToRad(-angleInt-90))) ;
result.x = round(centerPoint.x + self.radius * cos(ToRad(-angleInt-90)));
return result;
}
- (CGFloat)circleDiameter {
if(_handleType == EFSemiTransparentWhiteCircle) {
return _lineWidth;
} else if(_handleType == EFSemiTransparentBlackCircle) {
return _lineWidth;
} else if(_handleType == EFDoubleCircleWithClosedCenter) {
return _lineWidth * 2 + 3.5;
} else if(_handleType == EFDoubleCircleWithOpenCenter) {
return _lineWidth + 2.5 + 2;
} else if(_handleType == EFBigCircle) {
return _lineWidth + 2.5;
}
return 0;
}
static inline float AngleFromNorth(CGPoint p1, CGPoint p2, BOOL flipped) {
CGPoint v = CGPointMake(p2.x-p1.x,p2.y-p1.y);
float vmag = sqrt(SQR(v.x) + SQR(v.y)), result = 0;
v.x /= vmag;
v.y /= vmag;
double radians = atan2(v.y,v.x);
result = ToDeg(radians);
return (result >=0 ? result : result + 360.0);
}
-(float) valueFromAngle {
if(angle < 0) {
_currentValue = -angle;
} else {
_currentValue = 270 - angle + 90;
}
fixedAngle = _currentValue;
return (_currentValue*(_maximumValue - _minimumValue))/360.0f;
}
- (float)angleFromValue {
angle = 360 - (360.0f*_currentValue/_maximumValue);
if(angle==360) angle=0;
return angle;
}
- (CGFloat) widthOfString:(NSString *)string withFont:(UIFont*)font {
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
return [[[NSAttributedString alloc] initWithString:string attributes:attributes] size].width;
}
- (CGFloat) heightOfString:(NSString *)string withFont:(UIFont*)font {
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
return [[[NSAttributedString alloc] initWithString:string attributes:attributes] size].height;
}
#pragma mark - public methods
-(void)setInnerMarkingLabels:(NSArray*)labels{
labelsEvenSpacing = labels;
[self setNeedsDisplay];
}
#end
EFCircularSlider.h
#import <UIKit/UIKit.h>
#interface EFCircularSlider : UIControl
typedef NS_ENUM(NSInteger, EFHandleType) {
EFSemiTransparentWhiteCircle,
EFSemiTransparentBlackCircle,
EFDoubleCircleWithOpenCenter,
EFDoubleCircleWithClosedCenter,
EFBigCircle
};
#property (nonatomic) float minimumValue;
#property (nonatomic) float maximumValue;
#property (nonatomic) float currentValue;
#property (nonatomic) int lineWidth;
#property (nonatomic) int lineRadiusDisplacement;
#property (nonatomic, strong) UIColor* filledColor;
#property (nonatomic, strong) UIColor* unfilledColor;
#property (nonatomic, strong) UIColor* handleColor;
#property (nonatomic) EFHandleType handleType;
#property (nonatomic, strong) UIFont* labelFont;
#property (nonatomic, strong) UIColor* labelColor;
#property (nonatomic, assign) NSInteger labelDisplacement;
#property (nonatomic) BOOL snapToLabels;
-(void)setInnerMarkingLabels:(NSArray*)labels;
#end
You can only do 1 CGContextDrawPath with the implicit path, and use kCGPathFill to fill it.
Also, if you want the translucent background color to not interfere with the overlaying arc:
draw the background first
use an actual transparent color (alpha < 1.0)
Do this:
CGContextAddArc(ctx, self.frame.size.width/2,
self.frame.size.height/2,
self.radius, 0, M_PI *2, 0);
CGContextClosePath(ctx);
CGContextSetFillColorWithColor(ctx,
[UIColor colorWithRed:0
green:.5 // dark green
blue:0
alpha:.25] // translucent
.CGColor );
CGContextDrawPath(ctx, kCGPathFill);
...before you do that (redefine CGContextAddArc)
CGContextAddArc(ctx, self.frame.size.width/2,
self.frame.size.height/2,
self.radius, 0, M_PI *2, 0);
CGContextClosePath(ctx);
[_unfilledColor setStroke];
CGContextSetLineWidth(ctx, _lineWidth);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
PS. Nice control. Make sources public when anti-aliased and ready!

iOS iPhone sleep / interval before an action

in an UIRotationGestureRecognizer i will do an action drawCircleView.
But this action should start max 10 times in a second.
UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotateContainerView:)];
rotation.delegate = self;
[_containerView addGestureRecognizer:rotation];
- (void)rotateContainerView:(UIRotationGestureRecognizer*)sender
{
static CGFloat initialScale;
if (sender.state == UIGestureRecognizerStateBegan) {
initialScale = (_Ro + 0.1)*-1;
_Rotation=0;
}
_Ro = (sender.rotation*-1);
sleep(.1);
[self drawCircleView];
}
I have test the following
sleep(0.1);
[NSThread sleepForTimeInterval:.1];
NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.1]];
[NSThread sleepForTimeInterval:.1];
But with all it seems that all the action goes in a queue.
How i can do this without a query queue?
Full code
//
// CLHoleEffect.m
//
// Created by Kevin Siml - Appzer.de on 2013/10/23.
// Copyright (c) 2013 Appzer.de. All rights reserved.
//
#import "CLSwirlEffect.h"
#import "UIView+Frame.h"
#interface CLSwirlCircle : UIView
#property (nonatomic, strong) UIColor *color;
#end
#interface CLSwirlEffect()
<UIGestureRecognizerDelegate>
#end
#implementation CLSwirlEffect
{
UIView *_containerView;
UIView *_container;
CLSwirlCircle *_circleView;
UILabel* circleLabel;
CGFloat _X;
CGFloat _Y;
CGFloat _R;
CGFloat _Ro;
CGFloat _Rotation;
}
#pragma mark-
+ (NSString*)defaultTitle
{
return NSLocalizedStringWithDefaultValue(#"CLSwirlEffect_DefaultTitle", nil, [CLImageEditorTheme bundle], #"Swirl", #"");
}
+ (BOOL)isAvailable
{
return ([UIDevice iosVersion] >= 5.0);
}
- (id)initWithSuperView:(UIView*)superview imageViewFrame:(CGRect)frame toolInfo:(CLImageToolInfo *)info
{
self = [super initWithSuperView:superview imageViewFrame:frame toolInfo:info];
if(self){
_containerView = [[UIView alloc] initWithFrame:frame];
[superview addSubview:_containerView];
_X = 0.5;
_Y = 0.5;
_R = 0.5;
_Ro = 0.5;
[self setUserInterface];
}
return self;
}
- (void)cleanup
{
[_containerView removeFromSuperview];
}
- (UIImage*)applyEffect:(UIImage*)image
{
CGFloat R = (_R + 0.1);
GPUImageSwirlFilter *stillImageFilter = [[GPUImageSwirlFilter alloc] init];
[stillImageFilter setAngle: _Ro];
[stillImageFilter setRadius:R];
[stillImageFilter setCenter:CGPointMake(_X,_Y)];
UIImage *quickFilteredImage = [stillImageFilter imageByFilteringImage:image];
return quickFilteredImage;
}
#pragma mark-
- (void)setUserInterface
{
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapContainerView:)];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panContainerView:)];
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(pinchContainerView:)];
UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotateContainerView:)];
pan.maximumNumberOfTouches = 1;
tap.delegate = self;
pan.delegate = self;
pinch.delegate = self;
rotation.delegate = self;
[_containerView addGestureRecognizer:tap];
[_containerView addGestureRecognizer:pan];
[_containerView addGestureRecognizer:pinch];
[_containerView addGestureRecognizer:rotation];
_circleView = [[CLSwirlCircle alloc] init];
_circleView.backgroundColor = [UIColor clearColor];
_circleView.color = [UIColor whiteColor];
[_containerView addSubview:_circleView];
[self drawCircleView];
}
#define DEGREES_TO_RADIANS(x) (M_PI * x / 180.0)
- (void)drawCircleView
{
CGFloat R = MIN(_containerView.width, _containerView.height) * (_R + 0.1) * 1.2;
_circleView.width = R;
_circleView.height = R;
_circleView.center = CGPointMake(_containerView.width * _X, _containerView.height * _Y);
[_circleView setNeedsDisplay];
[self.delegate effectParameterDidChange:self];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
// if the gesture recognizers are on different views, don't allow simultaneous recognition
if (gestureRecognizer.view != otherGestureRecognizer.view)
return NO;
// if either of the gesture recognizers is the long press, don't allow simultaneous recognition
if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
return NO;
return YES;
}
- (void)tapContainerView:(UITapGestureRecognizer*)sender
{
CGPoint point = [sender locationInView:_containerView];
_X = MIN(1.0, MAX(0.0, point.x / _containerView.width));
_Y = MIN(1.0, MAX(0.0, point.y / _containerView.height));
[self drawCircleView];
if (sender.state == UIGestureRecognizerStateEnded){
[self.delegate effectParameterDidChange:self];
}
}
- (void)panContainerView:(UIPanGestureRecognizer*)sender
{
CGPoint point = [sender locationInView:_containerView];
_X = MIN(1.0, MAX(0.0, point.x / _containerView.width));
_Y = MIN(1.0, MAX(0.0, point.y / _containerView.height));
[self drawCircleView];
if (sender.state == UIGestureRecognizerStateEnded){
//[self.delegate effectParameterDidChange:self];
}
}
- (void)pinchContainerView:(UIPinchGestureRecognizer*)sender
{
static CGFloat initialScale;
if (sender.state == UIGestureRecognizerStateBegan) {
initialScale = (_R + 0.1);
}
_R = MIN(1.1, MAX(0.1, initialScale * sender.scale)) - 0.1;
[self drawCircleView];
if (sender.state == UIGestureRecognizerStateEnded){
// [self.delegate effectParameterDidChange:self];
}
}
- (void)rotateContainerView:(UIRotationGestureRecognizer*)sender
{
static CGFloat initialScale;
if (sender.state == UIGestureRecognizerStateBegan) {
initialScale = (_Ro + 0.1)*-1;
_Rotation=0;
}
_Ro = (sender.rotation*-1);
[self drawCircleView];
if (sender.state == UIGestureRecognizerStateEnded){
// [self.delegate effectParameterDidChange:self];
}
}
#end
#pragma mark- UI components
#implementation CLSwirlCircle
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
[self setNeedsDisplay];
}
- (void)setCenter:(CGPoint)center
{
[super setCenter:center];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rct = self.bounds;
rct.origin.x += 1;
rct.origin.y += 1;
rct.size.width -= 2;
rct.size.height -= 2;
CGContextSetStrokeColorWithColor(context, self.color.CGColor);
CGContextStrokeEllipseInRect(context, rct);
self.alpha = 1;
[UIView animateWithDuration:kCLEffectToolAnimationDuration
delay:1
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
self.alpha = 0;
}
completion:^(BOOL finished) {
}
];
}
#end
To restrict the method, rotateContainerView:, being called no more than ten times in a second you could timestamp the first call and compare each subsequent calls timestamp, allowing the method to complete only if the calls timestamp is greater than last calls timestamp plus 0.1 seconds.
Add a property of type NSDate -
#property (nonatomic, strong) NSDate *lastCall;
Then modify rotateContainerView: to something like this -
- (void)rotateContainerView:(UIRotationGestureRecognizer*)sender
{
static CGFloat initialScale;
NSDate *nowCall = [NSDate date];// timestamp
if (sender.state == UIGestureRecognizerStateBegan) {
lastCall = nowCall;
initialScale = (_Ro + 0.1)*-1;
_Rotation=0;
_Ro = (sender.rotation*-1);
[self drawCircleView];
}
else {
if ([nowCall timeIntervalSinceDate:lastCall] > 0.1) {
_Ro = (sender.rotation*-1);
[self drawCircleView];
lastCall = nowCall;
}
}
}
drawRect is what's actually drawing the view. Decouple your data changes and the drawing changes. Draw your circles from drawRect, but change the rotation from a data method that you call instead of your current calls to drawCircleView. If you need to update on intervals, use a NSTimer to schedule calls to a rotate method. But leave the drawing of the actual circles up to drawRect. That way, you have guaranteed rotations, but the drawing happens all the time (which I think is what you want).
Does that make sense?

Setting CPU Difficulty for Pong

Second time on here hoping that someone can guide me in the right direction.
I'm trying to implement a Pong game within an app for iOS devices (iPhone/iPod/iPad) and it works just fine, except for the fact that I can't beat the CPU, let alone score a point. Here's the implementation .m code:
#import "BTM_pongGame.h"
#import <QuartzCore/QuartzCore.h>
#define STEP_DURATION 0.05
#define RESET_BALL_ANIMATION_DURATION 0.5f
#define MARGIN_WHERE_BALL_IS_LEAVING 40
//#define CPU_SKILL 20
#interface BTM_pongGame ()
#end
#implementation BTM_pongGame
#synthesize cpuSkill, ballShape;
- (void)viewDidLoad
{
[super viewDidLoad];
cpuSkill = [BT_strings getJsonPropertyValue:self.screenData.jsonVars nameOfProperty:#"cpuSkill" defaultValue:#"20"];
ballShape = [BT_strings getJsonPropertyValue:self.screenData.jsonVars nameOfProperty:#"ballShape" defaultValue:#"1"];
if ([ballShape isEqual: #"1"]) {
self.ball.layer.cornerRadius = self.ball.frame.size.width / 2;
} else {
//self.ball.layer.cornerRadius = self.ball.frame.size.width / 2;
}
}
- (void) wordsPerLineInfoShowAlert {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Choose Game"
message:#"Multiplayer or Single Player? You decide! Choose below."
delegate:self
cancelButtonTitle:#"Single Player"
otherButtonTitles:#"Go Back",#"Multi Player", nil];
[alert show];
}
- (void)alertView: (UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if([title isEqualToString:#"Single Player"]) {
[self resetPlayerScoreLabels];
[self updatePlayerScoreLabels];
self.mode = kGameSinglePlayer;
[self moveComputerPaddle];
//Disable panning of computer paddle
[self.playerOne.superview setGestureRecognizers:#[]];
[self resetBoardForNewRound];
} else if ([title isEqualToString:#"Multi Player"]) {
[self resetPlayerScoreLabels];
[self updatePlayerScoreLabels];
self.mode = kGameMultiPlayer;
[self resetBoardForNewRound];
} else if ( [title isEqualToString:#"Go Back"]){
[self navLeftTap];
}
}
// thanks to MrsVanBeveren for coding the reset score stuff
-(void)resetPlayerScoreLabels
{
playerOneScore = 0;
playerTwoScore = 0;
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self wordsPerLineInfoShowAlert];
[self startGameTimer];
}
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[_gameTimer invalidate];
}
-(void)quitPressed:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
-(void)startGameTimer
{
[_gameTimer invalidate];
_gameTimer = [NSTimer scheduledTimerWithTimeInterval:STEP_DURATION target:self selector:#selector(step:) userInfo:nil repeats:YES];
}
#pragma mark Game setup
/*
* Some maths to calculate a new starting direction
* By choosing mid left/right part of the unit circle
* we avoid getting straight up/down directions
*/
-(void)resetBallDirection
{
float randomUnity = arc4random_uniform(100)/100.0;
int horizontalDirection = (arc4random() % 2 ? 1 : -1);
float angle = M_PI_4 + randomUnity * M_PI_2;
float direction = horizontalDirection * angle;
dirX = sin(direction);
dirY = cos(direction);
}
-(void)resetBoardForNewRound
{
speed = self.view.frame.size.width / 50.0;
[self resetBallDirection];
[_gameTimer invalidate];
[UIView animateWithDuration:RESET_BALL_ANIMATION_DURATION animations:^{
self.ball.center = CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0);
} completion:^(BOOL finished){
[self startGameTimer];
}];
[self enlargeAnimation:self.ball];
}
-(void)updatePlayerScoreLabels
{
self.playerOneScoreLabel.text = [NSString stringWithFormat:#"%d",playerOneScore];
self.playerTwoScoreLabel.text = [NSString stringWithFormat:#"%d",playerTwoScore];
}
#pragma mark Paddle handling
- (IBAction)paddlePanned:(UIPanGestureRecognizer*)recognizer {
UIView *paddleWrapper = recognizer.view;
UIView *paddle = [[paddleWrapper subviews] lastObject];
switch([recognizer state]) {
case UIGestureRecognizerStateBegan: {
paddle.backgroundColor = UIColor.whiteColor;
}
break;
case UIGestureRecognizerStateChanged: {
CGPoint position = [recognizer locationInView:self.view];
CGFloat haldPaddleHeight = paddleWrapper.frame.size.height / 2.0;
CGPoint newCenter = paddleWrapper.center;
newCenter.y = position.y;
newCenter.y = MAX(haldPaddleHeight, newCenter.y);
newCenter.y = MIN(self.view.bounds.size.height - haldPaddleHeight, newCenter.y);
paddleWrapper.center = newCenter;
}
break;
case UIGestureRecognizerStateEnded: {
paddle.backgroundColor = UIColor.grayColor;
}
break;
default:
break;
}
}
#pragma mark Game loop
/*
* Game loop
* - Moves the ball and checks for obstacles
*/
- (void)step:(NSTimer*)timer
{
speed += 0.05;
[self checkForBallLevingSide];
CGPoint newCenter = self.ball.center;
CGFloat ballRadius = self.ball.frame.size.height / 2.0;
newCenter.x += dirX * speed;
newCenter.y += dirY * speed;
CGFloat upperEdge = ballRadius;
CGFloat bottomEdge = self.view.bounds.size.height - ballRadius;
// Bounce ball of top/bottom walls
if (newCenter.y <= upperEdge) {
dirY = ABS(dirY);
newCenter.y = upperEdge;
} else if (newCenter.y >= bottomEdge) {
dirY = -ABS(dirY);
newCenter.y = bottomEdge;
}
[UIView animateWithDuration:STEP_DURATION animations:^{
self.ball.center = newCenter;
}];
}
-(BOOL)didBallHitPaddle:(UIView *)paddle withinLimit:(CGFloat)limit
{
if (CGRectIntersectsRect(paddle.frame, self.ball.frame)){
[self deflectBallFromPaddle:paddle];
CGRect ballFrame = self.ball.frame;
ballFrame.origin.x = limit;
self.ball.frame = ballFrame;
return YES;
} else
return NO;
}
-(void)checkForBallLevingSide
{
float limitLeft = MARGIN_WHERE_BALL_IS_LEAVING;
float limitRight = self.view.bounds.size.width - self.ball.frame.size.width - MARGIN_WHERE_BALL_IS_LEAVING;
CGRect ballFrame = self.ball.frame;
CGFloat ballX = ballFrame.origin.x;
if (ballX < limitLeft) {
if (![self didBallHitPaddle:self.playerOne.superview withinLimit:limitLeft])
[self playerDidMiss:kPlayerOne];
}else if (ballX > limitRight) {
if (![self didBallHitPaddle:self.playerTwo.superview withinLimit:limitRight])
[self playerDidMiss:kPlayerTwo];
}
}
/*
* Calculates new dirX and dirY after the bounce.
* The longer from the paddle's middle the bigger the result angle gets. (Pong style)
*/
-(void)deflectBallFromPaddle:(UIView *)paddle
{
dirX *= -1;
CGFloat diff = self.ball.center.y - paddle.center.y;
float p = diff / paddle.frame.size.height * 2.0f;
dirY += p * 0.5;
}
-(void)playerDidMiss:(kPlayer)player
{
if (player == kPlayerOne) {
playerTwoScore++;
[self victoryShake:self.playerTwo];
[self enlargeAnimation:self.playerTwoScoreLabel];
}
else if (player == kPlayerTwo) {
playerOneScore++;
[self victoryShake:self.playerOne];
[self enlargeAnimation:self.playerOneScoreLabel];
}
[self resetBoardForNewRound];
[self updatePlayerScoreLabels];
}
-(void)moveComputerPaddle
{
UIView *paddle = self.playerOne.superview;
CGPoint cpuCenter = paddle.center;
CGFloat diff = self.ball.center.y - paddle.center.y;
CGFloat movement = MIN((int)cpuSkill, ABS(diff));
movement *= diff > 0 ? 1 : -1;
cpuCenter.y += movement;
[UIView animateWithDuration:0.1 animations:^{
self.playerOne.superview.center = cpuCenter;
} completion:^(BOOL b) {
[self moveComputerPaddle];
}];
}
#pragma mark Animation
-(void)victoryShake:(UIView *)view
{
CAKeyframeAnimation * anim = [ CAKeyframeAnimation animationWithKeyPath:#"transform" ] ;
anim.values = [ NSArray arrayWithObjects:
[ NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0f, -20.0f, 0.0f) ],
[ NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0f, 20.0f, 0.0f) ],
nil ] ;
anim.autoreverses = YES ;
anim.repeatCount = 4.0f ;
anim.duration = 0.07f ;
[view.layer addAnimation:anim forKey:nil];
}
-(void)enlargeAnimation:(UIView *)view
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(3.0, 3.0, 1.0)];
[animation setDuration:RESET_BALL_ANIMATION_DURATION * 0.5];
animation.repeatCount = 1.0f ;
[animation setAutoreverses:YES];
[view.layer addAnimation:animation forKey:nil];
}
#end
I've simply tried changing the number where it states #define CPU_SkILL and again #"cpuSkill" defaultValue:#"20"];, but I've had no luck with changing the number in any direction.
Any ideas?
Thank you in advance.
The cpuSkill variable controls the movement of the CPU-controlled paddle:
CGFloat movement = MIN((int)cpuSkill, ABS(diff));
So maybe the cpuSkill is higher than intended in every situation. Try much lower values or make some random little adjustment to movement after the calculation.
Also, you should ABS() the cpuSkill to avoid perfect moving when negative moving.

Resources