Accounting for current time when panning to scrub video - ios

This is the code related to panning/ scrubbable video. The current issue occurs when the second swipe gesture has a delta from the last position of the first swipe. In other words this code needs to take into account the current time of the video to prevent the skip.
```
func didSwipe(panGR: UIPanGestureRecognizer) {
let translation = panGR.translationInView(self.view)
var horizontalTranslation = Float(translation.x)
let durationInSeconds = Float(CMTimeGetSeconds(self.playerView.player.player.currentItem!.asset.duration))
// Using 275 as the limit for delta along x
let translationLimit: Float = 275
let minTranslation: Float = -1 * translationLimit
let maxTranslation: Float = translationLimit
if horizontalTranslation > maxTranslation {
horizontalTranslation = maxTranslation
}
if horizontalTranslation < minTranslation {
horizontalTranslation = minTranslation
}
let timeToSeekTo = normalize(horizontalTranslation , minDelta: minTranslation, maxDelta: maxTranslation, minDuration: 0, maxDuration: durationInSeconds)
print("horizontal translation \(horizontalTranslation) \n timeToSeekTo: \(timeToSeekTo)")
self.playerView.player.startScrubbing()
self.playerView.player.scrub(timeToSeekTo)
self.playerView.player.stopScrubbing()
}
func normalize(delta: Float, minDelta: Float, maxDelta: Float, minDuration: Float, maxDuration: Float) -> Float {
let result = ((delta - minDelta) * (maxDuration - minDuration) / (maxDelta - minDelta) + minDuration)
return result
}
```
I am setting the starting time to be exactly at half of the video length. This produces a good first swipe result in either direction. It has a noticeable skip on the second and subsequent swipes because it is not accounting for the current time of the video (i think).

Here's the code I wrote that employs the normalize function, and displays the current rate (and a Play icon that changes size according to the player rate:
- (IBAction)handlePanGesture:(UIPanGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {
[self.rateLabel setHidden:FALSE];
[self animatePlayerControl:#"▷" size:1.0];
[UIView animateWithDuration:0.375 animations:^{
(self.playerControlsLabel).alpha = 0.5f;
}];
} else if (sender.state == UIGestureRecognizerStateEnded) {
[self.rateLabel setHidden:FALSE]; // set to TRUE after testing or remove hiding altogether
//[self.delegate setRate:oldRate];
//[self animatePlayerControl:#"□" size:1.0];
//[_ChromaEpsilonGammaAppDelegate.playerViewController setRate:0.0];
//[_ChromaEpsilonGammaAppDelegate.playerViewController stop];
[UIView animateWithDuration:0.375 animations:^{
(self.playerControlsLabel).alpha = 0.0f;
}];
} else if (sender.state == UIGestureRecognizerStateChanged){
CGPoint location = [sender locationInView:self];
float nlx = ((location.x / ((CGRectGetMidX(self.frame) / (self.frame.size.width / 2.0)))) / (self.frame.size.width / 2.0)) - 1.0;
//float nly = ((location.y / ((CGRectGetMidY(self.view.frame) / (self.view.frame.size.width / 2.0)))) / (self.view.frame.size.width / 2.0)) - 1.0;
nlx = nlx * 2.0;
[self.delegate setRate:nlx];
if (nlx > 0.0) [self animatePlayerControl:#"▷" size:nlx];
else if (nlx < 0.0) [self animatePlayerControl:#"◁" size:fabs(nlx)];
(self.rateLabel).text = [NSString stringWithFormat:#"%.2f", [self.delegate rate]];
}
}
The label setup, which can go anywhere, not just in the drawRect method:
- (void)drawRect:(CGRect)rect {
(self.brightnessLabel).textColor = [UIColor colorWithWhite:1.0 alpha:1.0];
(self.brightnessLabel).font = [UIFont preferredFontForTextStyle:#"Apple Symbol"];
(self.brightnessLabel).textAlignment = NSTextAlignmentCenter;
(self.contrastLabel).textColor = [UIColor colorWithWhite:1.0 alpha:1.0];
(self.contrastLabel).font = [UIFont preferredFontForTextStyle:#"Apple Symbol"];
(self.contrastLabel).textAlignment = NSTextAlignmentCenter;
_attrsDictionary = #{NSFontAttributeName: [UIFont systemFontOfSize:24.0 weight:UIFontWeightLight]};
}
And, the auto-adjusting Player icon size code:
- (void)animatePlayerControl:(NSString *)labelText size:(float)size {
(self.playerControlsLabel).textColor = [UIColor colorWithWhite:1.0 alpha:1.0];
(self.playerControlsLabel).font = [UIFont preferredFontForTextStyle:#"Apple Symbol"];
(self.playerControlsLabel).textAlignment = NSTextAlignmentCenter;
NSDictionary *attrsDictionary = #{NSFontAttributeName: [UIFont systemFontOfSize:fabs(48.0 * size) weight:UIFontWeightUltraLight]};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:labelText attributes:attrsDictionary];
(self.playerControlsLabel).attributedText = attrString;
}
The player icon is simply a character from the Apple Symbol font.

Related

iOS UITextView putting character onto next line

In this picture with a UITextView saying "Coordinate Geometry", the "e" gets put onto the next line. There is no line space there. I have also tried this with a UILabel (I switched to UITextView to try and fix the problem but there was no difference).
Here is my code for my subclass of UITextView
- (instancetype)initWithFrame:(CGRect)frame text:(NSString *)text circular:(BOOL)isCircular {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor lightGrayColor];//[UIColor clearColor];
self.alpha = 0.5;
self.textColor = [Styles mainTextColour];
[self setFont:[Styles heading2Font]];
[self setTextAlignment:NSTextAlignmentCenter];
self.text = text;
self.editable = NO;
self.contentInset = UIEdgeInsetsMake(-8, 0, -8, 0);
[self resizeFontToFix];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
NSAssert(NO, #"Don't use BubbleText -initWithFrame:, use -initWithFrame: andText: instead");
return nil;
}
- (void)resizeFontToFix {
//Resize to fix
CGFloat fontSize = self.font.pointSize;
CGFloat minSize = 6.0 * [Styles sizeModifier];
while (fontSize > minSize && [self sizeThatFits:(CGSizeMake(self.frame.size.width, FLT_MAX))].height >= self.frame.size.height) {
fontSize -= 1.0;
self.font = [self.font fontWithSize:fontSize];
}
//this below resizes to fit and centres the resulting view
// [self resizeFrameAndCentre];
}
- (void)resizeFrameAndCentre {
CGRect originalFrame = self.frame;
[self sizeToFit];
CGPoint centre = self.center;
centre.x = originalFrame.origin.x + (originalFrame.size.width / 2);
centre.y = originalFrame.origin.y + (originalFrame.size.height / 2);
self.center = centre;
}

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.

change position of Slide To Cancel

I'm using SlideToCancel as per requirements to my project but i'm unable to find how to change the position of button.
I have gone through all the code but cannot figure.
Here is the main code:
import
#import "SlideToCancelViewController.h"
#interface SlideToCancelViewController()
- (void) setGradientLocations:(CGFloat)leftEdge;
- (void) startTimer;
- (void) stopTimer;
#end
static const CGFloat gradientWidth = 0.2;
static const CGFloat gradientDimAlpha = 0.5;
static const int animationFramesPerSec = 8;
#implementation SlideToCancelViewController
#synthesize delegate;
// Implement the "enabled" property
- (BOOL) enabled {
return slider.enabled;
}
- (void) setEnabled:(BOOL)enabled{
slider.enabled = enabled;
label.enabled = enabled;
if (enabled) {
slider.value = 0.0;
label.alpha = 1.0;
touchIsDown = NO;
[self startTimer];
} else {
[self stopTimer];
}
}
- (UILabel *)label {
// Access the view, which will force loadView to be called
// if it hasn't already been, which will create the label
(void)[self view];
return label;
}
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
// Load the track background
UIImage *trackImage = [UIImage imageNamed:#"sliderTrack.png"];
sliderBackground = [[UIImageView alloc] initWithImage:trackImage];
// Create the superview same size as track backround, and add the background image to it
UIView *view = [[UIView alloc] initWithFrame:sliderBackground.frame];
[view addSubview:sliderBackground];
// Add the slider with correct geometry centered over the track
slider = [[UISlider alloc] initWithFrame:sliderBackground.frame];
CGRect sliderFrame = slider.frame;
sliderFrame.size.width -= 46; //each "edge" of the track is 23 pixels wide
slider.frame = sliderFrame;
slider.center = sliderBackground.center;
slider.backgroundColor = [UIColor clearColor];
[slider setMinimumTrackImage:[UIImage imageNamed:#"sliderMaxMin-02.png"] forState:UIControlStateNormal];
[slider setMaximumTrackImage:[UIImage imageNamed:#"sliderMaxMin-02.png"] forState:UIControlStateNormal];
UIImage *thumbImage = [UIImage imageNamed:#"sliderThumb.png"];
[slider setThumbImage:thumbImage forState:UIControlStateNormal];
slider.minimumValue = 0.0;
slider.maximumValue = 1.0;
slider.continuous = YES;
slider.value = 0.0;
// Set the slider action methods
[slider addTarget:self
action:#selector(sliderUp:)
forControlEvents:UIControlEventTouchUpInside];
[slider addTarget:self
action:#selector(sliderDown:)
forControlEvents:UIControlEventTouchDown];
[slider addTarget:self
action:#selector(sliderChanged:)
forControlEvents:UIControlEventValueChanged];
// Create the label with the actual size required by the text
// If you change the text, font, or font size by using the "label" property,
// you may need to recalculate the label's frame.
NSString *labelText = NSLocalizedString(#"slide to cancel", #"SlideToCancel label");
UIFont *labelFont = [UIFont systemFontOfSize:24];
CGSize labelSize = [labelText sizeWithFont:labelFont];
label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, labelSize.width, labelSize.height)];
// Center the label over the slidable portion of the track
CGFloat labelHorizontalCenter = slider.center.x + (thumbImage.size.width / 2);
label.center = CGPointMake(labelHorizontalCenter, slider.center.y);
// Set other label attributes and add it to the view
label.textColor = [UIColor whiteColor];
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
label.font = labelFont;
label.text = labelText;
[view addSubview:label];
[view addSubview:slider];
// This property is set to NO (disabled) on creation.
// The caller must set it to YES to animate the slider.
// It should be set to NO (disabled) when the view is not visible, in order
// to turn off the timer and conserve CPU resources.
self.enabled = NO;
// Render the label text animation using our custom drawing code in
// the label's layer.
label.layer.delegate = self;
// Set the view controller's view property to all of the above
self.view = view;
// The view is retained by the superclass, so release our copy
[view release];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
[self stopTimer];
[sliderBackground release], sliderBackground = nil;
[slider release], slider = nil;
[label release], label = nil;
}
// UISlider actions
- (void) sliderUp: (UISlider *) sender
{
//filter out duplicate sliderUp events
if (touchIsDown) {
touchIsDown = NO;
if (slider.value != 1.0) //if the value is not the max, slide this bad boy back to zero
{
[slider setValue: 0 animated: YES];
label.alpha = 1.0;
[self startTimer];
}
else {
//tell the delagate we are slid all the way to the right
[delegate cancelled];
}
}
}
- (void) sliderDown: (UISlider *) sender
{
touchIsDown = YES;
}
- (void) sliderChanged: (UISlider *) sender
{
// Fade the text as the slider moves to the right. This code makes the
// text totally dissapear when the slider is 35% of the way to the right.
label.alpha = MAX(0.0, 1.0 - (slider.value * 3.5));
// Stop the animation if the slider moved off the zero point
if (slider.value != 0) {
[self stopTimer];
[label.layer setNeedsDisplay];
}
}
// animationTimer methods
- (void)animationTimerFired:(NSTimer*)theTimer {
// Let the timer run for 2 * FPS rate before resetting.
// This gives one second of sliding the highlight off to the right, plus one
// additional second of uniform dimness
if (++animationTimerCount == (2 * animationFramesPerSec)) {
animationTimerCount = 0;
}
// Update the gradient for the next frame
[self setGradientLocations:((CGFloat)animationTimerCount/(CGFloat)animationFramesPerSec)];
}
- (void) startTimer {
if (!animationTimer) {
animationTimerCount = 0;
[self setGradientLocations:0];
animationTimer = [[NSTimer
scheduledTimerWithTimeInterval:1.0/animationFramesPerSec
target:self
selector:#selector(animationTimerFired:)
userInfo:nil
repeats:YES] retain];
}
}
- (void) stopTimer {
if (animationTimer) {
[animationTimer invalidate];
[animationTimer release], animationTimer = nil;
}
}
// label's layer delegate method
- (void)drawLayer:(CALayer *)theLayer
inContext:(CGContextRef)theContext
{
// Set the font
const char *labelFontName = [label.font.fontName UTF8String];
// Note: due to use of kCGEncodingMacRoman, this code only works with Roman alphabets!
// In order to support non-Roman alphabets, you need to add code generate glyphs,
// and use CGContextShowGlyphsAtPoint
CGContextSelectFont(theContext, labelFontName, label.font.pointSize, kCGEncodingMacRoman);
// Set Text Matrix
CGAffineTransform xform = CGAffineTransformMake(1.0, 0.0,
0.0, -1.0,
0.0, 0.0);
CGContextSetTextMatrix(theContext, xform);
// Set Drawing Mode to clipping path, to clip the gradient created below
CGContextSetTextDrawingMode (theContext, kCGTextClip);
// Draw the label's text
const char *text = [label.text cStringUsingEncoding:NSMacOSRomanStringEncoding];
CGContextShowTextAtPoint(
theContext,
0,
(size_t)label.font.ascender,
text,
strlen(text));
// Calculate text width
CGPoint textEnd = CGContextGetTextPosition(theContext);
// Get the foreground text color from the UILabel.
// Note: UIColor color space may be either monochrome or RGB.
// If monochrome, there are 2 components, including alpha.
// If RGB, there are 4 components, including alpha.
CGColorRef textColor = label.textColor.CGColor;
const CGFloat *components = CGColorGetComponents(textColor);
size_t numberOfComponents = CGColorGetNumberOfComponents(textColor);
BOOL isRGB = (numberOfComponents == 4);
CGFloat red = components[0];
CGFloat green = isRGB ? components[1] : components[0];
CGFloat blue = isRGB ? components[2] : components[0];
CGFloat alpha = isRGB ? components[3] : components[1];
// The gradient has 4 sections, whose relative positions are defined by
// the "gradientLocations" array:
// 1) from 0.0 to gradientLocations[0] (dim)
// 2) from gradientLocations[0] to gradientLocations[1] (increasing brightness)
// 3) from gradientLocations[1] to gradientLocations[2] (decreasing brightness)
// 4) from gradientLocations[3] to 1.0 (dim)
size_t num_locations = 3;
// The gradientComponents array is a 4 x 3 matrix. Each row of the matrix
// defines the R, G, B, and alpha values to be used by the corresponding
// element of the gradientLocations array
CGFloat gradientComponents[12];
for (int row = 0; row < num_locations; row++) {
int index = 4 * row;
gradientComponents[index++] = red;
gradientComponents[index++] = green;
gradientComponents[index++] = blue;
gradientComponents[index] = alpha * gradientDimAlpha;
}
// If animating, set the center of the gradient to be bright (maximum alpha)
// Otherwise it stays dim (as set above) leaving the text at uniform
// dim brightness
if (animationTimer) {
gradientComponents[7] = alpha;
}
// Load RGB Colorspace
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
// Create Gradient
CGGradientRef gradient = CGGradientCreateWithColorComponents (colorspace, gradientComponents,
gradientLocations, num_locations);
// Draw the gradient (using label text as the clipping path)
CGContextDrawLinearGradient (theContext, gradient, label.bounds.origin, textEnd, 0);
// Cleanup
CGGradientRelease(gradient);
CGColorSpaceRelease(colorspace);
}
- (void) setGradientLocations:(CGFloat) leftEdge {
// Subtract the gradient width to start the animation with the brightest
// part (center) of the gradient at left edge of the label text
leftEdge -= gradientWidth;
//position the bright segment of the gradient, keeping all segments within the range 0..1
gradientLocations[0] = leftEdge < 0.0 ? 0.0 : (leftEdge > 1.0 ? 1.0 : leftEdge);
gradientLocations[1] = MIN(leftEdge + gradientWidth, 1.0);
gradientLocations[2] = MIN(gradientLocations[1] + gradientWidth, 1.0);
// Re-render the label text
[label.layer setNeedsDisplay];
}
- (void)dealloc {
[self stopTimer];
[self viewDidUnload];
[super dealloc];
}
#end
Sorry for posting heavy code stuff.
P.S.: I know AppStore doesn't accepts this code but i'm interested to learn & do more on QuartzCore graphics than using interface builders.
Check this answer https://stackoverflow.com/a/25894355/1344237
You have to remove the label.layer.delegate = self;

Calling UIGestureRecognizer from UITouch object

I am trying to associate a Gesture with UITouch object because I need to pass data through a target action Method.
I have something implemented but it is inefficient,it get 100% of the CPU processing, it is probably because it is coded incorrectly.
There is my instance method for the UITouch event which call the UIPangesture
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
return YES;
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:#selector(handlePan:)];
[self addGestureRecognizer: panGesture];
}
There is my instance method for the UIPAnGestureRecognizer
- (IBAction)handlePan:(UIPanGestureRecognizer *)event
{
if (event.numberOfTouches==1)
{
CGPoint lastPoint = [event locationOfTouch: event.numberOfTouches - 1 inView: event.view];
CGRect bounds = self.bounds;
CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGPoint delta = CGPointMake(lastPoint.x - center.x, lastPoint.y - center.y);
CGFloat angle = (delta.y == 0 ? delta.x >= 0 ? 0 : M_PI : atan2(delta.y, delta.x));
angle = fmod(angle, M_PI * 2.0);
angle += angle >= 0 ? 0 : M_PI * 2.0;
UIColor *color = [UIColor colorWithHue: angle / (M_PI * 2.0) saturation:1. brightness:1. alpha:1];
if ([color getRed: &r green: &g blue:&b alpha: &a]){
rValue = r;
gValue = g;
bValue = b;
[self setNeedsDisplay];
[self sendActionsForControlEvents:UIControlEventValueChanged];
[self updateLabels];
}
}
}
And there there the methods which call the action method to obtain the variables from the color picker
- (void)setup:(CsoundObj *)csoundObj
{
NSLog(#"Color value - R : %f G : %f : B %f", rValue, gValue, bValue);
channelPtrR = [csoundObj getInputChannelPtr:#"mix" channelType:CSOUND_CONTROL_CHANNEL];
channelPtrG = [csoundObj getInputChannelPtr:#"pitch" channelType:CSOUND_CONTROL_CHANNEL];
channelPtrB = [csoundObj getInputChannelPtr:#"c" channelType:CSOUND_CONTROL_CHANNEL];
cachedValueR = rValue;
cachedValueG = gValue;
cachedValueB = bValue;
self.cacheDirty = YES;
[self addTarget:self action:#selector(updateValueCache:) forControlEvents:UIControlEventValueChanged];
}
- (void)updateValueCache:(id)sender
{
cachedValueR = ((CustomView*)sender).rValue;
cachedValueG = ((CustomView*)sender).gValue;
cachedValueB = ((CustomView*)sender).bValue;
self.cacheDirty = YES;
}
- (void)updateValuesToCsound
{
if (self.cacheDirty) {
*channelPtrR = cachedValueR;
*channelPtrG = cachedValueG;
*channelPtrB = cachedValueB;
self.cacheDirty = NO;
}
}
- (void)updateValuesFromCsound
{
}
- (void)cleanup
{
[self removeTarget:self action:#selector(updateValueCache:) forControlEvents:UIControlEventValueChanged];
}
I know the problem it is on the instance method for the UITouch event which call the UIPangesture, any other way to do it more efficiently?
I think that you are calling this section of code way too often:
CGPoint lastPoint = [event locationOfTouch: event.numberOfTouches - 1 inView: event.view];
CGRect bounds = self.bounds;
CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGPoint delta = CGPointMake(lastPoint.x - center.x, lastPoint.y - center.y);
CGFloat angle = (delta.y == 0 ? delta.x >= 0 ? 0 : M_PI : atan2(delta.y, delta.x));
angle = fmod(angle, M_PI * 2.0);
angle += angle >= 0 ? 0 : M_PI * 2.0;
UIColor *color = [UIColor colorWithHue: angle / (M_PI * 2.0) saturation:1. brightness:1. alpha:1];
if ([color getRed: &r green: &g blue:&b alpha: &a]){
rValue = r;
gValue = g;
bValue = b;
[self setNeedsDisplay];
[self sendActionsForControlEvents:UIControlEventValueChanged];
[self updateLabels];
}
You are likely calling it when there is no change, or little change. There are two things you could do to reduce the number of calls. You can test for a significant change in the angle like this:
//class property
#property (nonatomic, assign) CGFloat lastAngle;
//In your code
CGFloat deltaAngle = fabs(self.lastAngle - angle)
if (deltaAngle > 0.5) { //select whatever delta works best
angle += angle >= 0 ? 0 : M_PI * 2.0;
UIColor *color = [UIColor colorWithHue: angle / (M_PI * 2.0) saturation:1. brightness:1. alpha:1];
if ([color getRed: &r green: &g blue:&b alpha: &a]){
rValue = r;
gValue = g;
bValue = b;
[self sendActionsForControlEvents:UIControlEventValueChanged];
[self updateLabels];
[self setNeedsDisplay];
}
}
Or you can test for time since the last update like this:
//class property
#property (nonatomic, retain) NSDate *lastTime;
//set it when the view loads
self.lastTime = [NSDate date];
//In your code
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:lastdate];
self.lastTime = [NSDate date];
if (interval > 0.05) { //select whatever time interval works best
angle += angle >= 0 ? 0 : M_PI * 2.0;
UIColor *color = [UIColor colorWithHue: angle / (M_PI * 2.0) saturation:1. brightness:1. alpha:1];
if ([color getRed: &r green: &g blue:&b alpha: &a]){
rValue = r;
gValue = g;
bValue = b;
[self sendActionsForControlEvents:UIControlEventValueChanged];
[self updateLabels];
[self setNeedsDisplay];
}
}
Also, are you sure you need the [self setNeedsDisplay] ?

Adding an indicator at the current touch position?

I am developing a color selector and using a circular gradient and a panGesture to pick the color.
Can I add a crosshair or something else to the current touch position?
I need the crosshair to be visible but it should not interfere with the gradient.
This is the code that adds the gradient :
- (void)viewDidLoad
{
[super viewDidLoad];
CGSize size = CGSizeMake(self.view.bounds.size.width, ((self.view.bounds.size.height)/2));
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width, size.height), YES, 0.0);
[[UIColor whiteColor] setFill];
UIRectFill(CGRectMake(0, 0,size.width,size.height));
int sectors = 180;
float radius = MIN(size.width, size.height)/2;
float angle = 2 * M_PI/sectors;
UIBezierPath *bezierPath;
for ( int i = 0; i < sectors; i++)
{
CGPoint center = CGPointMake(((size.width)/2), ((size.height)/2));
bezierPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:i * angle endAngle:(i + 1) * angle clockwise:YES];
[bezierPath addLineToPoint:center];
[bezierPath closePath];
UIColor *color = [UIColor colorWithHue:((float)i)/sectors saturation:1. brightness:1. alpha:1];
[color setFill];
[color setStroke];
[bezierPath fill];
[bezierPath stroke];
}
img = UIGraphicsGetImageFromCurrentImageContext();
gradientView = [[UIImageView alloc]initWithImage:img];;
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePan:)];
colorView = [[UIView alloc] init];
colorView.frame = CGRectMake(0, 00, 50, 50);
rText.textAlignment = NSTextAlignmentCenter;
gText.textAlignment = NSTextAlignmentCenter;
bText.textAlignment = NSTextAlignmentCenter;
saturationText.textAlignment = NSTextAlignmentCenter;
alphaText.textAlignment = NSTextAlignmentCenter;
brightnessText.textAlignment = NSTextAlignmentCenter;
rText.keyboardType = UIKeyboardTypeNumberPad;
gText.keyboardType = UIKeyboardTypeNumberPad;
bText.keyboardType = UIKeyboardTypeNumberPad;
saturationText.keyboardType = UIKeyboardTypeNumberPad;
alphaText.keyboardType = UIKeyboardTypeNumberPad;
brightnessText.keyboardType = UIKeyboardTypeNumberPad;
gradientView.frame = CGRectMake(0,0,size.width,size.height);
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:gradientView];
gradientView.userInteractionEnabled = YES;
[gradientView addGestureRecognizer: panGesture];
[self.view addSubview:colorView];
}
The handlePan method :
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender
{
if (sender.numberOfTouches)
{
CGSize size = CGSizeMake(self.view.bounds.size.width, (self.view.bounds.size.height)/2);
float radius = MIN(size.width, size.height)/2;
[alphaSlider addTarget:self action:#selector(changeOpacity:) forControlEvents:UIControlEventValueChanged];
[hellSlider addTarget:self action:#selector(changeBrightness:) forControlEvents:UIControlEventValueChanged];
[saturationSlider addTarget:self action:#selector(saturate:) forControlEvents:UIControlEventValueChanged];
[rSlider addTarget:self action:#selector(redSlider:) forControlEvents:UIControlEventValueChanged];
[gSlider addTarget:self action:#selector(greenSlider:) forControlEvents:UIControlEventValueChanged];
[bSlider addTarget:self action:#selector(blueSlider:) forControlEvents:UIControlEventValueChanged];
CGPoint lastPoint = [sender locationOfTouch: sender.numberOfTouches - 1 inView: gradientView];
CGPoint center = CGPointMake((size.width/2), (size.height /2));
CGPoint delta = CGPointMake(lastPoint.x - center.x, lastPoint.y - center.y);
CGFloat angle = (delta.y == 0 ? delta.x >= 0 ? 0 : M_PI : atan2(delta.y, delta.x));
angle = fmod(angle, M_PI * 2.0);
angle += angle >= 0 ? 0 : M_PI * 2.0;
if((lastPoint.x - center.x) + (lastPoint.y - center.y)/2 < radius)
{
UIColor *color = [UIColor colorWithHue: angle / (M_PI * 2.0) saturation:saturationSlider.value brightness:hellSlider.value alpha:alphaSlider.value];
if ([color getRed: &r green: &g blue:&b alpha: &a])
{
NSLog(#"Color value - R : %g G : %g : B %g", r*255, g*255, b*255);
}
float red = r;
float green = g;
float blue = b;
rText.text = [NSString stringWithFormat:#"%.0f",rSlider.value];
gText.text = [NSString stringWithFormat:#"%.0f",green*255];
bText.text = [NSString stringWithFormat:#"%.0f",blue*255];
colorView.backgroundColor = [UIColor colorWithRed:(rText.text.floatValue/255) green:(gText.text.floatValue/255) blue:(bText.text.floatValue/255) alpha:alphaSlider.value];
rSlider.value = red*255;
gSlider.value = green*255;
bSlider.value = blue*255;
alphaText.text = [NSString stringWithFormat:#"%.2f",alphaSlider.value];
brightnessText.text = [NSString stringWithFormat:#"%.2f",hellSlider.value];
saturationText.text = [NSString stringWithFormat:#"%.2f",saturationSlider.value];
}
}
}
Question is solved, thank you.
Well, you definitely 'can' add a crosshair at your current touch position.
I can tell you one of the many possible ways of doing this.
Implement the
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer
action handler.
Load an image of a crosshair into an UIImage (say overlayImage):
overlayImage =[UIImage imageNamed:#"crosshair.png"];
and set it into an UIView (say overlayView).
[This can be done in your viewDidLoad method itself.]
Now, add the following code to your handlePan method:
CGPoint translation = [recognizer translationInView:colorView]; //whichever view you want.
_overlayView.center = CGPointMake(_overlayView.center.x + translation.x,
_overlayView.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:colorView];
if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [recognizer velocityInView:colorView];
CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
CGFloat slideMult = magnitude / 800;
// NSLog(#"magnitude: %f, slideMult: %f", magnitude, slideMult);
float slideFactor = 0.1 * slideMult; // Increase for more of a slide
CGPoint finalPoint = CGPointMake(_overlayView.center.x + (velocity.x * slideFactor),
_overlayView.center.y + (velocity.y * slideFactor));
finalPoint.x = MIN(MAX(finalPoint.x, 0), colorView.bounds.size.width);
finalPoint.y = MIN(MAX(finalPoint.y, 0), colorView.bounds.size.height);
[UIView animateWithDuration:slideFactor*2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
_overlayView.center = finalPoint;
} completion:nil];
That should do the trick.
You may also try doing this by implementing the touchesBegan and touchesMoved method.

Resources