What is the full Keypath list for CABasicAnimation? - core-animation

I've looked in the documentation but I noticed it's missing some like "transform.scale.xy": [CoreAnimation Guide][1] is there a more complete list?

Here's everything I'm aware of in terms of animatable properties, keyPaths, and key-value coding extensions.
CALayer Animatable layer properties -- the other CALayer types below all inherit from CALayer, so these also apply to those:
anchorPoint
backgroundColor
backgroundFilters
borderColor
borderWidth
bounds
compositingFilter
contents
contentsRect
cornerRadius
doubleSided
filters
frame
hidden
mask
masksToBounds
opacity
position
shadowColor
shadowOffset
shadowOpacity
shadowPath
shadowRadius
sublayers
sublayerTransform
transform
zPosition
CAEmitterLayer animatable properties:
emitterPosition
emitterZPosition
emitterSize
CAGradientLayer animatable properties:
colors
locations
endPoint
startPoint
CAReplicatorLayer animatable properties:
instanceDelay
instanceTransform
instanceRedOffset
instanceGreenOffset
instanceBlueOffset
instanceAlphaOffset
CAShapeLayer animatable properties:
fillColor
lineDashPhase
lineWidth
miterLimit
strokeColor
strokeStart
strokeEnd
CATextLayer animatable properties:
fontSize
foregroundColor
CATiledLayer animatable properties:
I feel like tileSize is animatable, but documentation doesn't agree.
CATransform3D Key-Value Coding Extensions:
rotation.x
rotation.y
rotation.z
rotation
scale.x
scale.y
scale.z
scale
translation.x
translation.y
translation.z
CGPoint keyPaths:
x
y
CGSize keyPaths:
width
height
CGRect keyPaths:
origin
origin.x
origin.y
size
size.width
size.height
These are Appendix B of the Core Animation Programming Guide, and Appendix C of the same.

Related

Core Animation: set anchorPoint on 10.8 to rotate a layer about its center

NB: This is for a Cocoa app on OS X, NOT iOS.
I have a layer-backed NSButton (subclass of NSView). What I want to do is rotate that button using Core Animation. I'm using the following code to do it:
CABasicAnimation *a = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
a.fromValue = [NSNumber numberWithFloat:0];
a.toValue = [NSNumber numberWithFloat:-M_PI*2];
[_refreshButton.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
a.duration = 1.8; // seconds
a.repeatCount = HUGE_VAL;
[_refreshButton.layer addAnimation:a forKey:nil];
This works, EXCEPT that when it runs, the layer jumps down and to the left so that its center point is at the origin point of the NSView, which is the lower-left corner at (0,0). The layer then rotates about its center, but obviously the jumping-to-the-lower-left-corner is not acceptable.
So, after much reading, I found this line in the 10.8 API release notes:
On 10.8, AppKit will control the following properties on a CALayer
(both when "layer-hosted" or "layer-backed"): geometryFlipped, bounds,
frame (implied), position, anchorPoint, transform, shadow*, hidden,
filters, and compositingFilter. Use the appropriate NSView cover methods
to change these properties.
This means that AppKit is "ignoring" my call to -setAnchorPoint in the code above and, instead, is setting that anchor point to the NSView's origin (0,0).
My question is: how do I solve this? What is the "appropriate NSView cover method" to set the anchorPoint for the layer (I can't find such a method on NSView). At the end of the day, I just want my button to rotate around its center point, indefinitely.
I don't see any methods on NSView that are a direct “cover” for anchorPoint.
What I do see in the 10.8 release notes, besides what you quoted, is this:
The anchorPoint is also always set to be (0,0), …
The anchorPoint controls which point of the layer is at position in the superlayer's coordinate system. NSView sets self.layer.anchorPoint to (0,0), which means the layer's lower-left corner is at self.layer.position.
When you set anchorPoint to (0.5,0.5), that means the center of the layer should be at the layer's position. Since you didn't modify position, this has the effect of moving the layer down and to the left, as you are seeing.
You need to compute the position you want the layer to have when its anchorPoint is (0.5,0.5), like this:
CGRect frame = _refreshButton.layer.frame;
CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
_refreshButton.layer.position = center;
_refreshButton.layer.anchorPoint = CGPointMake(0.5, 0.5);
I was experiencing the exact same problem as #Bryan in Swift: the object would jump away from its original position during animation. Here's the code for a pulsing NSButton for macOS:
let frame : CGRect = startButton.layer!.frame
let center : CGPoint = CGPoint(x: frame.midX, y: frame.midY)
startButton.layer?.position = center;
startButton.layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let pulse = CASpringAnimation(keyPath: "transform.scale")
pulse.duration = 0.2
pulse.fromValue = 0.95
pulse.toValue = 1.0
pulse.autoreverses = true
pulse.repeatCount = 10
pulse.initialVelocity = 0.5
pulse.damping = 1.0
startButton.layer?.add(pulse, forKey: "pulse")

NSRect rounded corners

Is there a simple way to add rounded corners to NSRect elements in Objective-C? Currently we're applying a PNG image that simulates corners to this:
NSRect newFrame = NSMakeRect(0, 0, size.width, size.height);
But, performance becomes an issue because there are many instances of this NSRect along with the image being rendered with Core Animation. Perhaps rendering a native NSRect with rounded edges would be better from a performance standpoint? Do said edges look smooth (anti-aliased) when rendered with Core Animation?
NSRect is a struct containing an NSPoint and an NSSize, so I think you mean anything that accepts NSRects (so subclasses of NSView). All NSView subclass layers respond appropriately to -cornerRadius (except something about NSScrollView).
self.view.layer.masksToBounds = YES;
self.view.layer.cornerRadius = 10.0;

How to resize(shrink/grow) UILabel on using pinch gesture recognizer?

I am using UILabel and have assigned pinch gesture recognizer to it. When I pinch the label I have written function which consists of the below code:
CGFloat scale = [(UIPinchGestureRecognizer*)sender scale];
label.font.pointSize = label.font.pointSize * scale;
This code increases/decreases the font size, but cannot resize the label. I have also used the below code to transform the label which transforms the whole label but the quality is lost:
CGAffineTransform newTransform = CGAffineTransformScale(currentTransform, scale, scale);
I need some work-around that can resize (width/height) of the label considering the font-size.
Any help would greatly be appreciated.

Mac Custom NSSlider how to override tick mark drawing outside slider frame

I'm creating a custom NSSlider where I want to draw labels underneath each of the tick marks.
I'm currently doing this in the custom NSSliderCell -(NSRect)rectOfTickMarkAtIndex however because the height of NSSlider is fixed, the label I'm drawing underneath is being cropped.
Anyone have any ideas?
Also any resources with full implementations of custom NSSliders would be appreciated.
Simply set the frame and bounds of the NSSlider (which is really a subclass of NSView) so it is higher. Then your drawing should work fine. Stick this code in awakeFromNib: (Replace slider with self if you're in its subclass.)
NSRect frameRect = [slider frame];
frameRect.size.height = 30;
[slider setFrame:frameRect];
NSRect boundsRect = [slider bounds];
boundsRect.size.height = 30;
[slider setBounds:boundsRect];

Getting the visible rect of an UIScrollView's content

How can I go about finding out the rect (CGRect) of the content of a displayed view that is actually visible on screen.
myScrollView.bounds
The code above works when there's no zooming, but as soon as you allow zooming, it breaks at zoom scales other than 1.
To clarify, I want a CGRect that contains the visible area of the scroll view's content, relative to the content. (ie. if it's a zoom scale 2, the rect's size will be half of the scroll view's size, if it's at zoom scale 0.5, it'll be double.)
Or you could simply do
CGRect visibleRect = [scrollView convertRect:scrollView.bounds toView:zoomedSubview];
Swift
let visibleRect = scrollView.convert(scrollView.bounds, to: zoomedSubview)
Answering my own question, mostly thanks to Jim Dovey's answer, which didn't quite do the trick, but gave me the base for my answer:
CGRect visibleRect;
visibleRect.origin = scrollView.contentOffset;
visibleRect.size = scrollView.bounds.size;
float theScale = 1.0 / scale;
visibleRect.origin.x *= theScale;
visibleRect.origin.y *= theScale;
visibleRect.size.width *= theScale;
visibleRect.size.height *= theScale;
The main difference is that the size of the visibleRect ought to be scrollView.bounds.size, rather than scrollView.contentSize which is the size of the content view. Also simplified the math a bit, and didn't quite see the use for the isless() which would break the code whenever it's greater.
You have to compute it using UIScrollView's contentOffset and contentSize properties, like so:
CGRect visibleRect;
visibleRect.origin = scrollView.contentOffset;
visibleRect.size = scrollView.contentSize;
You can then log it for sanity-testing:
NSLog( #"Visible rect: %#", NSStringFromCGRect(visibleRect) );
To account for zooming (if this isn't already done by the contentSize property) you would need to divide each coordinate by the zoomScale, or for better performance you would multiply by 1.0 / zoomScale:
CGFloat scale = (CGFloat) 1.0 / scrollView.zoomScale;
if ( isless(scale, 1.0) ) // you need to #include <math.h> for isless()
{
visibleRect.origin.x *= scale;
visibleRect.origin.y *= scale;
visibleRect.size.width *= scale;
visibleRect.size.height *= scale;
}
Aside: I use isless(), isgreater(), isequal() etc. from math.h because these will (presumably) do the right thing regarding 'unordered' floating-point comparison results and other weird & wonderful architecture-specific FP cases.
Edit: You need to use bounds.size instead of contentSize when calculating visibleRect.size.
Shorter version:
CGRect visibleRect = CGRectApplyAffineTransform(scrollView.bounds, CGAffineTransformMakeScale(1.0 / scrollView.zoomScale, 1.0 / scrollView.zoomScale));
I'm not sure if this is defined behavior, but almost all UIView subclasses have the origin of their bounds set to (0,0). UIScrollViews, however, have the origin set to contentOffset.
a little more general solution would be:
[scrollView convertRect:scrollView.bounds
toView:[scrollView.delegate viewForZoomingInScrollView:scrollView]];
CGRect visibleRect;
visibleRect.origin = scrollView.contentOffset;
visibleRect.size = scrollView.frame.size;
Swift 4.0:
My answer adapts Trenskow's answer to Swift 4.0:
let visible = scrollView.convert(scrollView.bounds, to: subView)
where scrollView is the view of the scroll, and subView is the view inside scrollView which is zoomable and contains all the contents inside the scroll.
I don't think that a UIScrollView gives you that rectangle directly, but I think you have all the necessary items to calculate it.
A combination of the bounds, the contentOffset and the zoomScale should be all you need to create the rectangle you are looking for.