CAKeyframeAnimation - core-animation

Hi i'm creating a Keyframe animation from multiple images. My problem is i would like the animation to instantly change from one image to the next rather than a fade.
CALayer *animLayer = [CALayer layer];
animLayer.bounds = CGRectMake(0, 0, width, height);
animLayer.position = CGPointMake(0, 0);
CAKeyframeAnimation *customFrameAnimation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
NSArray *sizeValues = [NSArray arrayWithObjects:(id)image1, (id)image2, nil];
NSArray *times = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0f], [NSNumber numberWithFloat:0.5f], nil];
NSArray *timingFunctions = [NSArray arrayWithObjects: [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault], nil];
[customFrameAnimation setValues:sizeValues];
[customFrameAnimation setKeyTimes:times];
customFrameAnimation.duration=5.0;
customFrameAnimation.beginTime = 1e-100;
customFrameAnimation.fillMode = kCAFillModeRemoved;
customFrameAnimation.timingFunctions = timingFunctions;
customFrameAnimation.removedOnCompletion = YES;
[animLayer addAnimation:customFrameAnimation forKey:nil];
Thanks in advance.

Your animation will need its calculationMode set to kCAAnimationDiscrete.
Take a look at the documentation on keyTimes which describes how the calculationMode is used:
The appropriate values in the keyTimes
array are dependent on the
calculationMode property.
If the calculationMode is set to
kCAAnimationLinear, the first value in
the array must be 0.0 and the last
value must be 1.0. Values are
interpolated between the specified key
times.
If the calculationMode is set
to kCAAnimationDiscrete, the first
value in the array must be 0.0.
If the
calculationMode is set to
kCAAnimationPaced or
kCAAnimationCubicPaced, the keyTimes
array is ignored.
If the values in the
keyTimes array are invalid or
inappropriate for the calculationMode,
the keyTimes array is ignored.
And then you can read the description of the calculation modes:
Value calculation modes
These constants are used by the
calculationMode property.
NSString * const kCAAnimationLinear;
NSString * const kCAAnimationDiscrete;
NSString * const kCAAnimationPaced;
Constants
kCAAnimationLinear
Simple linear
calculation between keyframe values.
Available in Mac OS X v10.5 and later.
Declared in CAAnimation.h.
kCAAnimationDiscrete
Each keyframe value is used in turn, no interpolated
values are calculated.
Available in
Mac OS X v10.5 and later.
Declared in CAAnimation.h.
kCAAnimationPaced
Keyframe values are interpolated to produce an even
pace throughout the animation.
Available in Mac OS X v10.5 and later.
Declared in CAAnimation.h.
In other words, the discrete calculation mode makes the animation jump to each key frame rather than animate/transition to it.
Best regards.

Related

With what should I replace the deprecated sizeWithFont: method?

I have a method that gives me the perfect size for a UITextView given a length of string (with the corresponding correct font size) :
- (NSInteger) heightOfLabel:(NSString*) string {
CGSize maximumLabelSize = CGSizeMake([[UIScreen mainScreen] bounds].size.width - 40, FLT_MAX);
CGSize expectedLabelSize = [[NSString stringTrimmedForLeadingAndTrailingWhiteSpacesFromString:string]
sizeWithFont:[UIFont systemFontOfSize:15]
constrainedToSize:maximumLabelSize
lineBreakMode:NSLineBreakByWordWrapping];
return expectedLabelSize.height + 5;
}
In fact, it still gives me a perfect fit, even in iOS7. Although now it comes up with a warning method that says I shouldn't use 'sizeWithFont:contrainedToSize:lineBreakMode'.
It now says I should be using -boundingRectWithSize:options:attributes:context:
This method isn't new to iOS7 and therefore i figure that it is okay to ask it on stack overflow, rather than going across to the official apple developers forum.
I have three questions:
1) Because it is deprecated, does that mean I should definitely replace it, despite it still working?
2) I have tried many different boundingRectWithSize: methods, with various variables but it is never perfect, it always seems to be slightly out (as many stackoverflow questions point out) - is there a perfect replacement with this none-deprecated method that does exactly the same as my previous method with as minimal hassle?
3) why remove this method? Is it because of the overlap with this other method?
After an hour of trial error I managed to make it work:
CGSize maximumLabelSize = CGSizeMake(tableView.width, MAXFLOAT);
NSStringDrawingOptions options = NSStringDrawingTruncatesLastVisibleLine |
NSStringDrawingUsesLineFragmentOrigin;
NSDictionary *attr = #{NSFontAttributeName: [UIFont systemFontOfSize:15]};
CGRect labelBounds = [string boundingRectWithSize:maximumLabelSize
options:options
attributes:attr
context:nil];
Update:
As Mr. T mentions in answer below : In iOS 7 and later, this method returns fractional sizes (in the size component of the returned CGRect); to use a returned size to size views, you must use raise its value to the nearest higher integer using the ceil function. ceilf function is recommended to use.
CGFloat height = ceilf(labelBounds.size.height);
I believe the function was deprecated because that series of NSString+UIKit functions were based on the UIStringDrawing library, which wasn't thread safe. If you tried to run them not on the main thread (like any other UIKit functionality), you'll get unpredictable behaviors. In particular, if you ran the function on multiple threads simultaneously, it'll probably crash your app. This is why in iOS 6, they introduced a the boundingRectWithSize:... method for NSAttributedStrings. This was built on top of the NSStringDrawing libraries and is thread safe.
If you look at the new NSString boundingRectWithSize:... function, it asks for an attributes array in the same manner as a NSAttributeString. If I had to guess, this new NSString function in iOS 7 is merely a wrapper for the NSAttributeString function from iOS 6.
On that note, if you were only supporting iOS 6 and iOS 7, then I would definitely change all of your NSString's sizeWithFont:... to the NSAttributeString's boundingRectWithSize. It'll save you a lot of headache if you happen to have a weird multi-threading corner case! Here's how I converted NSString's sizeWithFont:constrainedToSize::
What used to be:
NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
CGSize size = [text sizeWithFont:font
constrainedToSize:CGSizeMake(width, CGFLOAT_MAX)];
Can be replaced with:
NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
NSAttributedString *attributedText =
[[NSAttributedString alloc]
initWithString:text
attributes:#
{
NSFontAttributeName: font
}];
CGRect rect = [attributedText boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
CGSize size = rect.size;
Please note the documentation mentions:
In iOS 7 and later, this method returns fractional sizes (in the size
component of the returned CGRect); to use a returned size to size
views, you must use raise its value to the nearest higher integer
using the ceil function.
So to pull out the calculated height or width to be used for sizing views, I would use:
CGFloat height = ceilf(size.height);
CGFloat width = ceilf(size.width);
For linebreak issue:
- (CGFloat)heightNeededForText:(NSString *)text withFont:(UIFont *)font width:(CGFloat)width lineBreakMode:(NSLineBreakMode)lineBreakMode {
NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = lineBreakMode;
CGSize size = [text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{ NSFontAttributeName: font, NSParagraphStyleAttributeName: paragraphStyle }
context:nil].size;
return ceilf(size.height);
}
Swift version of the Alexander of Norway's answer...
func heightNeededForText(text: NSString, withFont font: UIFont, width: CGFloat, lineBreakMode:NSLineBreakMode) -> CGFloat {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = lineBreakMode
let size: CGSize = text.boundingRectWithSize(CGSizeMake(width, CGFloat.max), options: [.UsesLineFragmentOrigin, .UsesFontLeading], attributes: [ NSFontAttributeName: font, NSParagraphStyleAttributeName: paragraphStyle], context: nil).size//text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MA
return ceil(size.height);
}
In the code where you want to get the height just call the method like below...
let size = self.heightNeededForText(text as NSString, withFont: UIFont.systemFontOfSize(15.0), width: scrollView.frame.size.width - 20, lineBreakMode: NSLineBreakMode.ByWordWrapping) //Can edit the font size and LinebreakMode

Cocos2d getChildByTag Not Returning Sprite

I am trying to change the text of a CCLabelTTF in cocos2d xcode (objective-c). I am setting the label like this:
CCLabelTTF *progressLBL = [CCLabelTTF labelWithString:#"connecting..." fontName:#"Marker Felt" fontSize:10];
progressLBL.position = ccp( width + 4, (s.height) - hight - 15);
CCMenu *menuHolder = [CCMenu menuWithItems:publishingLinesButton , nil];
[self addChild:progressLBL z:10 tag:cnt];
s is just the hight and width of the screen and cnt if an integer that goes up each time from 1 to 13. Then about 5 seconds after the label is created i get it like this:
CCLabelTTF *progressLBL = (CCLabelTTF *)[self getChildByTag:[dataInfo objectAtIndex:0]];
progressLBL.string = #"Updated";
dataInfo is an array and the object at index 0 is an integer. However when i run this code the labels are not changed. I have also tried:
CCLabelTTF *progressLBL = (CCLabelTTF *)[self getChildByTag:4];
But still the label is not changed.
Thanks, Sorry for wasting your time if this is something supper simple.
The fact is that an Objective-C array contains objects, it cannot contain primitive types. The tag argument is an integer, and you're passing an object instead (probably you got a compiler warning). I suppose that the object is a NSNumber, so you should take it's value calling the intValue accessor:
CCLabelTTF *progressLBL = (CCLabelTTF *)[self getChildByTag:[dataInfo objectAtIndex:0].intValue ];
Which with the newer compilers syntax can be translated like this:
CCLabelTTF *progressLBL = (CCLabelTTF *)[self getChildByTag: dataInfo[0].intValue ];

Collection element of type 'CGSize' is not an Objective-C object

I'm trying a give a NSDictionary key value a CGSize, and XCode is giving me this error. My code:
NSArray *rows = #[
#{#"size" : (CGSize){self.view.bounds.size.width, 100}
}
];
Why am I getting an error here? If its impossible to store a CGSize in a dict, then what's an alternative approach?
EDIT:
now getting "Used type 'CGSize' (aka 'struct CGSize') where arithmetic, pointer, or vector type is required" error with this code:
NSDictionary *row = rows[#0];
CGSize rowSize = [row[#"size"] CGSizeValue] ? : (CGSize){self.view.bounds.size.width, 80};
CGSize is not an object. It's a C struct. If you need to store this in an obj-c collection the standard way is by wrapping it in NSValue, like so:
NSArray *rows = #[
#{#"size" : [NSValue valueWithCGSize:(CGSize){self.view.bounds.size.width, 100}]
}
];
Then later when you need to get the CGSize back, you just call the -CGSizeValue method, e.g.
CGSize size = [rows[0][#"size"] CGSizeValue];
Kevin's answer only works on iOS; for Mac OS X (Cocoa) do:
[NSValue valueWithSize:NSMakeSize(self.view.bounds.size.width, 100)];

Objective C IOS - int to specific string based on value of slider

So i have this slider in my view and that has a max of 13 and a min of 1. Dependent on how you move the slider the number changes and can be shown on a label that shows the current number the slider is on.
I want to be able to change the int from the slider into a string; so if the progress of the slider is 13 i want the label to read "A+", 12 with "A" 11 with "A-" and so on.
Without having to make 13 "if" statements because i have more than one slider, is there a way to make a method that checks the progress of the slider then converts the number to the desired letter grade?
here is my code for one of the sliders
-(IBAction)sliderChanged:(id)sender{
UISlider *slider = (UISlider *)sender;
int progress = (int)roundf(slider.value);
c1Grade.text = [NSString stringWithFormat:#"%d",progress];
}
// c1Grade is the label next to the slider showing the progress
Make an array of strings, and place the description at the index the description represents: #"A+" would be at index 13, "A" at index 12, and so on.
NSArray *gradeDescriptions = [NSArray arrayWithObjects: #"", #"F", #"D-", #"D", #"D+", #"C-", #"C", #"C+", #"B-", #"B", #"B+", #"A-", #"A", #"A+", nil];
NSString *descr = [gradeDescriptions objectAt:sliderPosition];
Since gradeDescriptions array never changes, you can initialize it once, and store in a static variable or an ivar.

Comparing Touch Coordinates

Is it possible to compare touch coordinates made by the users on the UIView to the one store in a plist or txt format? The argument looks like this;
if (user touch coordinate == touch coordinate stored in plist or text)
then
(do something)
else
(do something)
If possible in what format should i write the coordinates in the list and how to associate it inside the program?
thanks in advance and sorry if you find my question a bit noobie.
Not sure if there's a one-liner solution.
On a UITouch instance, the locationInView: method returns a CGPoint struct (x and y coordinates, both of type float). So you can store the x and y coordinates in your plist, then compare them with your current touch's x and y coordinates.
EDIT:
Also, when comparing the coordinates, you probably want to use the distance between the two points to determine when you have a "hit".
EDIT:
Below is sample code for loading and writing to a property list, where the values are based on a NSDictionary:
- (NSMutableDictionary *)loadDictionaryFromPList: (NSString *)plistName
{
NSString *plistPath = [[NSBundle mainBundle] pathForResource:plistName ofType:#"plist"];
NSDictionary *immutableDictionary = [NSDictionary dictionaryWithContentsOfFile: plistPath];
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary: immutableDictionary];
return mutableDictionary;
}
- (void)saveDictionary: (NSDictionary *)mySettings toPList: (NSString *)plistName
{
NSString *plistPath = [[NSBundle mainBundle] pathForResource:plistName ofType:#"plist"];
[mySettings writeToFile: plistPath atomically: YES];
}
The method to calculate the distance between the two locations of the UITouches:
-(CGFloat) distanceBetween: (CGPoint) point1 and: (CGPoint)point2
{
CGFloat dx = point2.x - point1.x;
CGFloat dy = point2.y - point1.y;
return sqrt(dx*dx + dy*dy );
}
And finally, the code that uses the values in the property list to determine if the user hit the previous location:
CGPoint currentTouchLocation = [currentTouch locationInView:self];
// Lookup last Touch location from plist, and handle case when current Touch matches it:
NSMutableDictionary *mySettings = [self loadDictionaryFromPList: #"MySettings"];
NSNumber *lastXCoordinate = [mySettings objectForKey:#"lastXCoordinate"];
NSNumber *lastYCoordinate = [mySettings objectForKey:#"lastYCoordinate"];
if (lastXCoordinate && lastYCoordinate)
{
CGPoint lastTouchLocation = CGPointMake([lastXCoordinate floatValue], [lastYCoordinate floatValue]);
CGFloat distanceBetweenTouches = [self distanceBetween: currentTouchLocation and: lastTouchLocation];
if (distanceBetweenTouches < 25) // 25 is just an example
{
// Handle case where current touch is close enough to "hit" previous one
NSLog(#"You got a hit!");
}
}
// Save current touch location to property list:
[mySettings setValue: [NSNumber numberWithFloat: currentTouchLocation.x] forKey: #"lastXCoordinate"];
[mySettings setValue: [NSNumber numberWithFloat: currentTouchLocation.y] forKey: #"lastYCoordinate"];
[self saveDictionary:mySettings toPList: #"MySettings"];
The functions you're probably looking for are NSStringFromCGPoint() and CGPointFromString().
But two touch coordinates will almost certainly never be the exact same. You should almost never be comparing CGFloats with ==, let alone ones you get from such an analog input as a finger touch. You need to compare whether they are "close enough." See this blog for a good example of how to measure the distance between two points. You want that result to be less than some value (epsilon, or "a small number") that is appropriate for your purposes.