CABasicAnimation and custom types - objective-c

I'm not very familiar with CoreAnimation, so I hope I've just missed something pretty simple. I want to animate a custom property (NSGradient) of a NSView in a simple manner, with [[view animator] setGradient:gradient];. I defined + (id)defaultAnimationForKey:(NSString *)key and returned a simple CABasicAnimation, however, no animation is executed. Since this works for simpler types and NSColor, I guess CABasicAnimation doesn't work with gradients. Fine, but in this particular case gradients are trivial (two stops, always), so I can easily write an interpolation functions. The question: how can I define a custom interpolation? I googled around regarding delegates on view, layer and animations, subclassing animation class etc., but I wasn't able to figure the things out. Thanks!

I thought I remembered passing by some Apple documentation when I was learning how to use Core Animation that showed how to set up animations that couldn't be handled by properticode describedes that are supplied with defined animations. Along the way I stumbled across some sample code from Apple that is described as:
A single gradient layer is displayed and continuously animated using new random colors.
That may be the answer to the specific task you already handled another way. I found it in the Documentation and API Reference within Xcode and the name of the sample code is simply Gradients. (Note that there is an original version 1.0 and an updated version 1.1 that was redone this year in April and so should be easier to use with current tools.
But, the larger question of creating a custom animation that can't be automated by Core Animation itself is to follow the example from Apple's Animation Programming Guide for Cocoa in the section Using an NSAnimation Object. It's described under the topic Subclassing NSAnimation and the recommended method is shown under the heading Smooth Animations. You override the setCurrentProgress: method so that each time it is called you first invoke Super so that NSAnimation updates the progress value, i.e., your custom animated property and then do any updating or drawing needed for the next frame of your animation. Here are the notes and example code provided by Apple in the referenced documentation:
As mentioned in “Setting and Handling Progress Marks,” you can attach a series of progress marks to an NSAnimation object and have the delegate implement the animation:didReachProgressMark: method to redraw an object at each progress mark. However, this is not the best way to animate an object. Unless you set a large number of progress marks (30 per second or more), the animation is probably going to appear jerky.
A better approach is to subclass NSAnimation and override the setCurrentProgress: method, as illustrated in Listing 4. The NSAnimation object invokes this method after each frame to change the progress value. By intercepting this message, you can perform any redrawing or updating you need for that frame. If you do override this method, be sure to invoke the implementation of super so that it can update the current progress.
Listing 4 Overriding the setCurrentProgress: method
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
// Call super to update the progress value.
[super setCurrentProgress:progress];
// Update the window position.
NSRect theWinFrame = [[NSApp mainWindow] frame];
NSRect theScreenFrame = [[NSScreen mainScreen] visibleFrame];
theWinFrame.origin.x = progress *
(theScreenFrame.size.width - theWinFrame.size.width);
[[NSApp mainWindow] setFrame:theWinFrame display:YES animate:YES];
}
So basically you define a "progress value" (possibly composed of several values) that defines the state of your custom animation and write code that given the current "progress value" draws or changes what is drawn when the animation is at that particular state. Then you let NSAnimation run the animation using the normal methods of setting up an animation and it will execute your code to draw each frame of the animation at the appropriate time.
I hope that answers what you wanted to know. I doubt I could have found this easily by searching without having seen it before since I finally had to go to where I thought it might be and skim page by page through the entire topic to find it again!

Related

Xcode (Obj-C) Updating a UIView (NSRect) after the window is already visible to the user

I am fairly new to creating Xcode projects using Objective-C and i'm trying to make a simple 2d graphics program to show me how well my graphing code works before I implement it elsewhere.
I have gotten the drawing of everything right but the problem comes when I want to clear the strokes and stroke new lines. From what I see from when I run the program the view will only display what I have executed once it hits that #end at the end of the implementation. The issue with this is that by the time it hits the #end the code has already been run. and I can't figure out if I need to recall the class in a loop to update the view each time (or where or how to do this, perhaps something in main.m?) or if I need to call a function to update the view before it exits the implementation because right now all the lines are just overwriting each other before the user can see anything.
Here is the interface in my header file (connected to a UIView):
#interface GraphView : NSView
- (void)drawRect:(NSRect)dirtyRect;
#end
Implementation file:
Here is how I am creating my rectangle:
- (void)drawRect:(NSRect)dirtyRect {
[self clearGrid:dirtyRect];
[self drawLines];
}
- (void)clearGrid:(NSRect)theRect {
//Wiping the slate clean
[super drawRect:theRect];
[self.window setBackgroundColor:[NSColor whiteColor]];
...
Here is what I am using to draw my lines:
NSBezierPath* eqLine = [NSBezierPath bezierPath];
[[NSColor greenColor] setStroke];
[eqLine setLineWidth:5.0];
[eqLine moveToPoint:NSMakePoint([self convertToPixels:previousX], [self convertToPixels:previousY])];
[eqLine lineToPoint:NSMakePoint([self convertToPixels:finalX], [self convertToPixels:finalY])];
[eqLine stroke];
I have been searching for the past few days now on how I could solve this but so far it hasn't turned up anything, perhaps i'm just not searching for the right thing. Any information is helpful even if it's just a point to a resource that I can look at. Let me know if any additional information is needed. Thanks!
It's clear that you're a noobie to this, which is not a crime. So there's a lot to address here. Let's take things one at a time.
To answer your basic question, the -drawRect: method is called whenever a view needs to draw its graphic representation, whatever that is. As long as nothing changes that would alter its graphical representation, the -drawRect: method will only be received once.
To inform Cocoa (or CocoaTouch) that the graphic representation has changed, you invalidate all, or a portion of, your view. This can be accomplished in many ways, but the simplest is by setting the needsDisplay property to YES. Once you do that, Cocoa will (at some point in the future) call the -drawRect: method again to modify the graphic representation of your view. Wash, rinse, repeat.
So here's the basic flow:
You create your class and add it to a view. Cocoa draws your view the first time when it appears by sending your class a -drawRect: message.
If you want your view to change, you invalidate a portion of the view (i.e. view.needsDisplay = YES). Then you just sit back and wait. Very soon, Cocoa will send -drawRect: again, your class draws the new and improved image, and it appears on the screen.
Many changes will cause your view to be invalidated, and subsequently redrawn, automatically—say, if it's resized. But unless you make a change that Cocoa knows will require your view to redraw itself, you'll have to invalidate the view yourself.
Now for the nit-picking, which I hope you understand is all an effort to help you understand what's going on and improve your code...
Your subject line says UIView but your code example subclasses NSView so I'm actually not sure if you're writing a macOS or an iOS app. It doesn't matter too much, because at this level the differences are minimal.
Your -drawRect: calls [self clearGrid:..., when then calls [super drawRect:... Don't do this. As a rule, never use super except from within the overloaded method of the same name. In other words, -drawRect: can use [super drawRect:, but no other methods should. It's not "illegal", but it will save you grief in the long run.
Your -clearGrid: method sets the backgroundColor of the window. Don't do this. The window's background color is a property, and your -drawRect: method should only be drawing the graphical representation of your view—nothing more, nothing less.
You're calling [super drawRect: from within a direct subclass of NSView (or UIView). While that's OK, it's unnecessary. Both of these base classes clearly document that their -drawRect: method does nothing, so there's nothing to be gained by calling it. Again, it won't cause any harm, it's just pointless. When your -drawRect: method begins execution, a graphics context has already been set up, cleared, and is ready to draw into. Just start drawing.
#end is not a statement. It does not get "executed". It's just a word that tell the compiler that the source code for your class has come to an end. The stuff that gets executed are the methods, like -drawRect:.
In your #interface section you declared a -drawRect: method. This is superfluous, because the -drawRect: method is already declared in the superclass.

Why should I use the block-based animation rather than begin/end animation?

As you know, Apple encourages us to use a new method called block-based animation about animation over iOS 4.0.
I really wonder what block-based animation is better than begin/end style animation.
performance?
concurrency?
coding efficiency and convenience?
I wondered about this too back then.
But after using block based animations like this:
[UIView animateWithDuration:0.5 ... ^{
// animated custom view vertically
} completion:^{
[UIView animateWithDuration:0.5 ... ^{
// animate the fade in alpha of buttons
}];
}];
It provides the completion handler in a short concise manner. You can also nest sub animation blocks within each other.
With BeginAnimation/EndAnimation, I don't recall exactly how to do a callback for completion handler, but you usually do something like:
// begin animation
// set delegate
// create delegate callback function for each beginAnimation
Now imagine if you wanted to nest 3 or 4 levels animation, such as as replicating the CSS Lightbox effect:
1) Fade in Lightbox container
2) Expand Width
3) Expand Height
4) Fade in form
You'd have to deal with some pretty messy if-else condition.
Your workflow would be like:
"After this beginAnimation finish, it sends a message to my callback method, scrolls down Xcode to find the callback delegate method, then in the callback method it calls another UIView beginAnimation, scroll back up Xcode to find the next beginAnimation ... "
With block based animation, each process is encapsulated in a block which you can nest in another block. If you decided you want to change the order things appear such that:
1) Fade in Lightbox container
2) Expand Height before Width this time
3) Expand Width after height this time
4) Fade in form
With the beginAnimation approach, you'll start pulling your hairs out.
Hope that helps.
Largely convenience.
There's little in the way of performance improvements that can be made from using a block. So it's unlikely anything in that. I'd have thought that all the block syntax for animations does is to call through to the old methods (or effectively do that through calling similar internal methods) and just run the block in between where you do beginAnimation and commitAnimation.
So, convenience. I suggest using it for that reason alone anyway. It's a lot easier to use and makes it easy to nest animations and do things on completion as you don't need to create other methods to call upon completion - it's just another block.

Apples ZoomingPDFViewer Example - Object creation

I'm currently working on an App which should display and allow users to zoom a PDF page.
Therefore I was looking on the Apple example ZoomingPDFViewer.
Basically I understand the sample code.
But a few lines are not obvious to me.
Link to the sample code:
http://developer.apple.com/library/ios/#samplecode/ZoomingPDFViewer/Introduction/Intro.html
in PDFView.m:
//Set the layer's class to be CATiledLayer.
+ (Class)layerClass {
return [CATiledLayer class];
}
What does the code above do?
And the second code snippet I don't understand in PDFView.m again:
self = [super initWithFrame:frame];
if (self) {
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
...
I know it creates a CATiledLayer object. But how it will be created is not clear to me.
I hope someone could give me a short answer to my question because I don't want to use code which I don't understand.
Thank you!
The TiledPDFView.h class is a subclass of UIView, so you can see what documentation UIView has on that method. According to the docs I see, it looks like:
layerClass - Implement this method only if you want your view to use a different Core Animation layer for its backing store. For example, if you are using OpenGL ES to do your drawing, you would want to override this method and return the CAEAGLLayer class.
So it seems that it is asking the Core Animation system to use a tiled-layer. Further docs from CATiledLayer:
CATiledLayer is a subclass of CALayer providing a way to
asynchronously provide tiles of the layer's content, potentially
cached at multiple levels of detail.
As more data is required by the renderer, the layer's drawLayer:inContext: method is called on one or more background
threads to supply the drawing operations to fill in one tile of data.
The clip bounds and CTM of the drawing context can be used to
determine the bounds and resolution of the tile being requested.
Regions of the layer may be invalidated using the setNeedsDisplayInRect: method however the update will be asynchronous.
While the next display update will most likely not contain the updated
content, a future update will.

Fade continuously between two CAGradientLayer color set-ups?

I want a UIButton to pulse, and I plan to do this by fading slowly between two color arrays on a CAGradientLayer. Is there a way to repeat an animation back and forth?
Yes. In Core Animation you create an explicit animation to do the fade and then you must also set two other properties for the animation object: autoReverses and repeatCount (number of repetitions to perform, each 2 repetitions will take you through your animation and back again). In your case you’d add the following lines to your code that sets up the animation object (I’ll call the object anim):
anim.repeatCount = HUGE_VALF;
anim.autoReverses = YES;
HUGE_VALF causes the animation to repeat forever though you could specify a number larger than any amount of repetitions that might occur.
These properties aren’t shown in the documentation of the CAAnimation object or it’s subclasses since it is defined in the CAMediaTiming Protocol which is adopted by CAAnimation and it's subclasses. But you can see examples and discussion of the CAMediaTiming protocol as it applies to CAAnimation objects in the Timing, Timespaces, and CAAnimation section of the Animation Types and Timing Programming Guide either on Apple’s Developer site or in the documentation provided through XCode.
(Many people seem to find Apple's Core Animation documentation to be particularly hard to understand until you get a good overall grasp of the disparate parts. I basically knew what you had to do but still found it hard to remember exactly where to find the actual information as to the properties involved.)

Fitting Cocoa Animation into MVC/OOP patterns

MVC/OOP design patterns say you don't set a property, per se, you ask an object to set its property. Similarly, in Cocoa you don't tell an object when to draw itself. Your object's code has detailed HOW it will draw itself so we trust the frameworks to decide when (for the most part) it should draw.
But, when it comes to animation in Cocoa (specifically Cocoa-Touch) it seems that we now must take control of when the object draws itself from within the objects view controller. I can't send a message to a UIView subclass asking it to change some value and then leave it alone knowing it will slowly (duration = X) animate itself to a new position, alpha, rotation, etc. depending on the property changes. Or can I?
Basically, I'm looking for a way to set the property and then walk away. Instead, it seems, I need to wrap the code that calls the object asking it to change its property with an animation block of some sort "[UIView beginAnimations:nil context:NULL]; ... [UIView commitAnimations];"
I'm ending up with lots and lots of animation blocks in my view controllers and none in my view objects...I guess I'm just looking for someone to verify that this is how things are done and I'm not overlooking something. I haven't gotten much farther than the UIView animations within Cocoa-Touch, so maybe that's my problem and it's time to dig deeper?!?
You are correct that UIView does not animate its property changes by default the way CALayer does, but I don't think this indicates a break in MVC. It is appropriate for a Controller to instruct a View in how it should transform. That is the role of a Controller class as surely as it is appropriate for the Controller to know the correct frame for the View and even manage layout. I agree that it's a little weird that you call -beginAnimations:context: on the UIView class rather than on an instance, but in practice it does actually work much better that way since you may want to animate many views together.
That said, if you had a UIView subclass that managed the layout of its subviews, there would be nothing wrong with allowing that UIView to manage the animation rather than relying on a UIViewController to do it. So this is something that could go either place, but in practice it generally goes in the Controller as you've discovered.
I am using "MVC" here in the typical Cocoa sense. You're correct that this might not be appropriate in a SmallTalk program, but then SmallTalk Controllers have a much more limited role (management of user input events). Cocoa significantly expands the role of Controllers in MVC and I think it's an improvement, even if it means there are now some functions that could go in either the Controller or the View (and this is one of them).