iOS 7 - CoreAnimation not working for UICollectionViewCell - ios7

I have a simple animation of shaking of cell icons in UICollectionView, similar to spring board edit mode. Animation was working well in iOS 6, but not working in iOS 7.
Here is the sample code.
- (void)startQuivering
{
CABasicAnimation *quiverAnim = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
float startAngle = (-1) * M_PI/180.0;
float stopAngle = -startAngle;
quiverAnim.fromValue = [NSNumber numberWithFloat:startAngle];
quiverAnim.toValue = [NSNumber numberWithFloat:3 * stopAngle];
quiverAnim.autoreverses = YES;
quiverAnim.duration = 0.12;
quiverAnim.repeatCount = HUGE_VALF;
float timeOffset = (float)(arc4random() % 100)/100 - 0.50;
quiverAnim.timeOffset = timeOffset;
CALayer *layer = self.layer;
[layer addAnimation:quiverAnim forKey:#"quivering"];
}
Similarly Stoping the quivering animation.
- (void)stopQuivering
{
CALayer *layer = self.layer;
[layer removeAnimationForKey:#"quivering"];
}
Calling these methods in applyLayoutAttributes: method in my custom UICollectionViewCell class, depending on long press gesture and related flag
I am unable to figure out the issue, so I need help from the developers.

Finally Solved the issue. Its similar to this question How to refresh UICollectionViewCell in iOS 7? -applyLayoutAttributes: was not getting called properly, could solve the issue by overriding isEqual: method in custom UICollectionViewLayoutAttributes subclass and calling super -applyLayoutAttributes:.

Related

Custom Spinner class with rotation animation problem

I programmed my own view containing an imageview which should be rotating. Here is my rotation animation:
- (void)startPropeller
{
//_movablePropeller = [[UIImageView alloc] initWithFrame:self.frame];
//_movablePropeller.image = [UIImage imageNamed:#"MovablePropeller"];
//[self addSubview:self.movablePropeller];
self.hidden = NO;
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotation.fromValue = [NSNumber numberWithFloat:0.0f];
rotation.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
rotation.cumulative = true;
rotation.duration = 1.2f; // Speed
rotation.repeatCount = INFINITY; // Repeat forever. Can be a finite number.
[self.movablePropeller.layer removeAllAnimations];
[self.movablePropeller.layer addAnimation:rotation forKey:#"Spin"];
}
And here is how I start it:
self.loadingPropeller = [[FMLoadingPropeller alloc] initWithFrame:self.view.frame andStyle:LoadingPropellerStyleNoBackground];
self.loadingPropeller.center=self.view.center;
[self.view addSubview:self.loadingPropeller];
[self.loadingPropeller startPropeller];
Problem is: Without any further code. The propeller is not rotating. So I was able to solve it by adding this code into my class implementing to rotating propeller spinner:
-(void)viewDidAppear:(BOOL)animated
{
if(!self.loadingPropeller.hidden){
[self.loadingPropeller startPropeller];
}
}
But I don't like that too much. Isn't it possible to add some code within the Propeller class to solve this issue automatically, without having to add also code in every class in the viewDidAppear method?
The code that doesn't work does two essential things: adding the spinner to the view hierarchy and positioning it. My guess is that the failure is due to positioning it before layout has happened. Try this:
// in viewDidLoad of the containing vc...
self.loadingPropeller = [[FMLoadingPropeller alloc] initWithFrame:CGRectZero andStyle:LoadingPropellerStyleNoBackground];
[self.view addSubview:self.loadingPropeller];
// within or after viewDidLayoutSubviews...
// (make sure to call super for any of these hooks)
self.loadingPropeller.frame = self.view.bounds;
self.loadingPropeller.center = self.view.center;
// within or after viewDidAppear (as you have it)...
[self.loadingPropeller startPropeller];

Animating CALayer shadow simultaneously as UITableviewCell height animates

I have a UITableView that I am attempting to expand and collapse using its beginUpdates and endUpdates methods and have a drop shadow displayed as that's happening. In my custom UITableViewCell, I have a layer which I create a shadow for in layoutSubviews:
self.shadowLayer.shadowColor = self.shadowColor.CGColor;
self.shadowLayer.shadowOffset = CGSizeMake(self.shadowOffsetWidth, self.shadowOffsetHeight);
self.shadowLayer.shadowOpacity = self.shadowOpacity;
self.shadowLayer.masksToBounds = NO;
self.shadowLayer.frame = self.layer.bounds;
// this is extremely important for performance when drawing shadows
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.shadowLayer.frame cornerRadius:self.cornerRadius];
self.shadowLayer.shadowPath = shadowPath.CGPath;
I add this layer to the UITableViewCell in viewDidLoad:
self.shadowLayer = [CALayer layer];
self.shadowLayer.backgroundColor = [UIColor whiteColor].CGColor;
[self.layer insertSublayer:self.shadowLayer below:self.contentView.layer];
As I understand it, when I call beginUpdates, an implicit CALayerTransaction is made for the current run loop if none exists. Additionally, layoutSubviews also gets called. The problem here is that the resulting shadow is drawn immediately based on the new size of the UITableViewCell. I really need to shadow to continue to cast in the expected way as the actual layer is animating.
Since my created layer is not a backing CALayer it animates without explicitly specifying a CATransaction, which is expected. But, as I understand it, I really need some way to grab hold of beginUpdates/endUpdates CATransaction and perform the animation in that. How do I do that, if at all?
So I guess you have something like this:
(I turned on “Debug > Slow Animations” in the simulator.) And you don't like the way the shadow jumps to its new size. You want this instead:
You can find my test project in this github repository.
See #horseshoe7's answer for a Swift translation.
It is tricky but not impossible to pick up the animation parameters and add an animation in the table view's animation block. The trickiest part is that you need to update the shadowPath in the layoutSubviews method of the shadowed view itself, or of the shadowed view's immediate superview. In my demo video, that means that the shadowPath needs to be updated by the layoutSubviews method of the green box view or the green box's immediate superview.
I chose to create a ShadowingView class whose only job is to draw and animate the shadow of one of its subviews. Here's the interface:
#interface ShadowingView : UIView
#property (nonatomic, strong) IBOutlet UIView *shadowedView;
#end
To use ShadowingView, I added it to my cell view in my storyboard. Actually it's nested inside a stack view inside the cell. Then I added the green box as a subview of the ShadowingView and connected the shadowedView outlet to the green box.
The ShadowingView implementation has three parts. One is its layoutSubviews method, which sets up the layer shadow properties on its own layer to draw a shadow around its shadowedView subview:
#implementation ShadowingView
- (void)layoutSubviews {
[super layoutSubviews];
CALayer *layer = self.layer;
layer.backgroundColor = nil;
CALayer *shadowedLayer = self.shadowedView.layer;
if (shadowedLayer == nil) {
layer.shadowColor = nil;
return;
}
NSAssert(shadowedLayer.superlayer == layer, #"shadowedView must be my direct subview");
layer.shadowColor = UIColor.blackColor.CGColor;
layer.shadowOffset = CGSizeMake(0, 1);
layer.shadowOpacity = 0.5;
layer.shadowRadius = 3;
layer.masksToBounds = NO;
CGFloat radius = shadowedLayer.cornerRadius;
layer.shadowPath = CGPathCreateWithRoundedRect(shadowedLayer.frame, radius, radius, nil);
}
When this method is run inside an animation block (as is the case when the table view animates a change in the size of a cell), and the method sets shadowPath, Core Animation looks for an “action” to run after updating shadowPath. One of the ways it looks is by sending actionForLayer:forKey: to the layer's delegate, and the delegate is the ShadowingView. So we override actionForLayer:forKey: to provide an action if possible and appropriate. If we can't, we just call super.
It is important to understand that Core Animation asks for the action from inside the shadowPath setter, before actually changing the value of shadowPath.
To provide the action, we make sure the key is #"shadowPath", that there is an existing value for shadowPath, and that there is already an animation on the layer for bounds.size. Why do we look for an existing bounds.size animation? Because that existing animation has the duration and timing function we should use to animate shadowPath. If everything is in order, we grab the existing shadowPath, make a copy of the animation, store them in an action, and return the action:
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
if (![event isEqualToString:#"shadowPath"]) { return [super actionForLayer:layer forKey:event]; }
CGPathRef priorPath = layer.shadowPath;
if (priorPath == NULL) { return [super actionForLayer:layer forKey:event]; }
CAAnimation *sizeAnimation = [layer animationForKey:#"bounds.size"];
if (![sizeAnimation isKindOfClass:[CABasicAnimation class]]) { return [super actionForLayer:layer forKey:event]; }
CABasicAnimation *animation = [sizeAnimation copy];
animation.keyPath = #"shadowPath";
ShadowingViewAction *action = [[ShadowingViewAction alloc] init];
action.priorPath = priorPath;
action.pendingAnimation = animation;
return action;
}
#end
What does the action look like? Here's the interface:
#interface ShadowingViewAction : NSObject <CAAction>
#property (nonatomic, strong) CABasicAnimation *pendingAnimation;
#property (nonatomic) CGPathRef priorPath;
#end
The implementation requires a runActionForKey:object:arguments: method. In this method, we update the animation that we created in actionForLayer:forKey: using the saved-away old shadowPath and the new shadowPath, and then we add the animation to the layer.
We also need to manage the retain count of the saved path, because ARC doesn't manage CGPath objects.
#implementation ShadowingViewAction
- (void)runActionForKey:(NSString *)event object:(id)anObject arguments:(NSDictionary *)dict {
if (![anObject isKindOfClass:[CALayer class]] || _pendingAnimation == nil) { return; }
CALayer *layer = anObject;
_pendingAnimation.fromValue = (__bridge id)_priorPath;
_pendingAnimation.toValue = (__bridge id)layer.shadowPath;
[layer addAnimation:_pendingAnimation forKey:#"shadowPath"];
}
- (void)setPriorPath:(CGPathRef)priorPath {
CGPathRetain(priorPath);
CGPathRelease(_priorPath);
_priorPath = priorPath;
}
- (void)dealloc {
CGPathRelease(_priorPath);
}
#end
This is Rob Mayoff's answer written in Swift. Could save someone some time.
Please don't upvote this. Upvote Rob Mayoff's solution. It is awesome, and correct. (Note from mayoff: why not upvote both? 😉)
import UIKit
class AnimatingShadowView: UIView {
struct DropShadowParameters {
var shadowOpacity: Float = 0
var shadowColor: UIColor? = .black
var shadowRadius: CGFloat = 0
var shadowOffset: CGSize = .zero
static let defaultParameters = DropShadowParameters(shadowOpacity: 0.15,
shadowColor: .black,
shadowRadius: 5,
shadowOffset: CGSize(width: 0, height: 1))
}
#IBOutlet weak var contentView: UIView! // no sense in have a shadowView without content!
var shadowParameters: DropShadowParameters = DropShadowParameters.defaultParameters
private func apply(dropShadow: DropShadowParameters) {
let layer = self.layer
layer.shadowColor = dropShadow.shadowColor?.cgColor
layer.shadowOffset = dropShadow.shadowOffset
layer.shadowOpacity = dropShadow.shadowOpacity
layer.shadowRadius = dropShadow.shadowRadius
layer.masksToBounds = false
}
override func layoutSubviews() {
super.layoutSubviews()
let layer = self.layer
layer.backgroundColor = nil
let contentLayer = self.contentView.layer
assert(contentLayer.superlayer == layer, "contentView must be a direct subview of AnimatingShadowView!")
self.apply(dropShadow: self.shadowParameters)
let radius = contentLayer.cornerRadius
layer.shadowPath = UIBezierPath(roundedRect: contentLayer.frame, cornerRadius: radius).cgPath
}
override func action(for layer: CALayer, forKey event: String) -> CAAction? {
guard event == "shadowPath" else {
return super.action(for: layer, forKey: event)
}
guard let priorPath = layer.shadowPath else {
return super.action(for: layer, forKey: event)
}
guard let sizeAnimation = layer.animation(forKey: "bounds.size") as? CABasicAnimation else {
return super.action(for: layer, forKey: event)
}
let animation = sizeAnimation.copy() as! CABasicAnimation
animation.keyPath = "shadowPath"
let action = ShadowingViewAction()
action.priorPath = priorPath
action.pendingAnimation = animation
return action
}
}
private class ShadowingViewAction: NSObject, CAAction {
var pendingAnimation: CABasicAnimation? = nil
var priorPath: CGPath? = nil
// CAAction Protocol
func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
guard let layer = anObject as? CALayer, let animation = self.pendingAnimation else {
return
}
animation.fromValue = self.priorPath
animation.toValue = layer.shadowPath
layer.add(animation, forKey: "shadowPath")
}
}
Assuming that you're manually setting your shadowPath, here's a solution inspired by the others here that accomplishes the same thing using less code.
Note that I'm intentionally constructing my own CABasicAnimation rather than copying the bounds.size animation exactly, as in my own tests I found that toggling the copied animation while it was still in progress could cause the animation to snap to it's toValue, rather than transitioning smoothly from its current value.
class ViewWithAutosizedShadowPath: UIView {
override func layoutSubviews() {
super.layoutSubviews()
let oldShadowPath = layer.shadowPath
let newShadowPath = CGPath(rect: bounds, transform: nil)
if let boundsAnimation = layer.animation(forKey: "bounds.size") as? CABasicAnimation {
let shadowPathAnimation = CABasicAnimation(keyPath: "shadowPath")
shadowPathAnimation.duration = boundsAnimation.duration
shadowPathAnimation.timingFunction = boundsAnimation.timingFunction
shadowPathAnimation.fromValue = oldShadowPath
shadowPathAnimation.toValue = newShadowPath
layer.add(shadowPathAnimation, forKey: "shadowPath")
}
layer.shadowPath = newShadowPath
}
}
UITableView is likely not creating a CATransaction, or if it is, it's waiting until after you end the updates. My understanding is that table views just coalesce all changes between those functions and then creates the animations as necessary. You don't have a way to get a handle on the actual animation parameters it's committing, because we don't know when that actually happens. The same thing happens when you animate a content offset change in UIScrollView: the system provides no context about the animation itself, which is frustrating. There is also no way to query the system for current CATransactions.
Probably the best you can do is inspect the animation that UITableView is creating and just mimic the same timing parameters in your own animation. Swizzling add(_:forKey:) on CALayer can allow you to inspect all animations being added. You certainly don't want to actually ship with this, but I often use this technique in debugging to figure out what animations are being added and what their properties are.
I suspect that you're going to have to commit your own shadow layer animations in tableView(_:willDisplayCell:for:row:) for the appropriate cells.

Cocoa ScreenSaverView

I am trying to play with ScreenSaverView in mac OS X.
I followed this tutorial http://cocoadevcentral.com/articles/000088.php
and it worked (can't say flawlessly ,but worked).
I also saw in part 2 of this tutorial they are playing with openGL stuff etc.
I have 2 general questions:
1)Can I use SprteKit within screensaver view?
2)What is wrong with my code here -> it compiles well, however I don't see anything except a black screen (I want to see my *.png).
- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
self = [super initWithFrame:frame isPreview:isPreview];
if (self)
{
_imageView = [[NSImageView alloc]initWithFrame:[self bounds]];
[_imageView setImage:[NSImage imageNamed:#"SettingsButton.png"]];
[self addSubview:_imageView];
[self setAnimationTimeInterval:1.0];
}
return self;
}
EDIT: Attempt to use a draw rect:
- (void)drawRect:(NSRect)rect0
{
[super drawRect:rect0];
NSImage *anotherImage = [NSImage imageNamed:#"SettingsButton.png"];
[anotherImage drawAtPoint:NSMakePoint(10,100) fromRect:NSMakeRect(0,0,[anotherImage size].width,[anotherImage size].height) operation:NSCompositeCopy fraction:1.0];
// Calculate a random color
CGFloat red = SSRandomFloatBetween( 0.0, 255.0 ) / 255.0;
CGFloat green = SSRandomFloatBetween( 0.0, 255.0 ) / 255.0;
CGFloat blue = SSRandomFloatBetween( 0.0, 255.0 ) / 255.0;
NSColor* color = [NSColor colorWithCalibratedRed:red
green:green
blue:blue
alpha:1];
NSColor * color2 = [NSColor colorWithPatternImage:anotherImage];
[color2 set];
NSBezierPath * path = [NSBezierPath bezierPathWithRect:rect0];
[path fill];
}
When I set a color it works , I can see a screen filled by Random colors (fine as meant)
When I use color2 which is a pattern from the image
nothing is works :-( -> I tried different images
same nothing there...
I checked in build phase that I do copy the images as a bundle resources
What could be the problem ?
EDIT: Ok so after my attempt in drawRect I suspected that imageNamed method is causing a troubles and rewrite my origin attempt to:
- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
self = [super initWithFrame:frame isPreview:isPreview];
if (self)
{
_imageView = [[NSImageView alloc]initWithFrame:[self bounds]];
NSBundle * tempBundle = [NSBundle bundleForClass:[self class]];
// Load your image, "top.tiff". Don't forget to release it when you are done (not shown).
NSImage * theImage = [[NSImage alloc] initWithContentsOfFile:
[tempBundle pathForImageResource:#"personal1.jpg"]];
[_imageView setImage:theImage];
[self addSubview:_imageView];
[self setAnimationTimeInterval:1.0];
}
return self;
}
And Whoaallaaa it worked !!(Kinda) I see my picture :-) !!!
As for part1 of the Question -> Yes it is possible
Just tried and it works !
with a little down side I can exit ScreenSaver only with cmd key xD
Managed to solve this issue,by subclassing SKView and delivering event to the next responder.
However it gave me a huge idea -> actually it just opens an opportunity to make a simple SK game as part of the screensaver could have been a cute feature.
Sadly I can't submit it to app store :)
It's not clear to me if a SpriteKit view (SKView) can even be used in a normal OSX/iOS app. I have searched in the past and found nothing.
If SpriteKit is anything like other game frameworks (from which it borrows much of its structure) then it will use a traditional game loop; i.e. clear screen, draw everything, wait a bit and then repeat.
Cocoa apps use an runloop that reacts to events and take steps to only redraw what needs to be redrawn.
So I would say "No" to your first question.
As far as your second, code-related, question is concerned, then I cannot see much wrong with it, however I am not familiar with that init method; what class does it subclass?

REFrostedViewController and iOS 8

I'm not sure if anyone has a fix for this but I've been trying to use this library I have everything setup but can't seem to achieve the blur like in the demo. I have navigation setup and working correctly but I cannot set a blur or blur tint color. I'm able to customize direction and a couple other properties but the blur does not seem to be working. The only thing I can think of is that this does not work for iOS 8. Here is my RootViewController's awakeFromNib:
- (void)awakeFromNib {
self.contentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"homeController"];
self.menuViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"sideMenuController"];
self.liveBlur = NO;
self.liveBlurBackgroundStyle = REFrostedViewControllerLiveBackgroundStyleLight;
self.blurRadius = 60;
self.blurTintColor = [UIColor colorWithRed:45/255.0 green:55/255.0 blue:56/255.0 alpha:0.6];
}
If you need just iOS8 blur you can use this code. There you are creating VisualEffectView with blur effect and then set frame to this view.
UIVisualEffect *effect;
effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
// effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
// effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight];
UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:effect];
blurEffectView.frame = self.view.frame;
[self.view addSubview:self.blurEffectView];
But you can also use UIImage+ImageEffects apples library for that case.
UPDATE
That's a result: blur on view controller and effected UILabel on top.

Rotate NSButton clockwise with animation

I'm trying to rotate an NSButton clockwise until a user manually interrupts it. Here's the code I'm using to accomplish this. I know it used to work at some point. Any idea how to fix it? Thanks in advance!
CABasicAnimation *a = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
a.fromValue = [NSNumber numberWithFloat:0];
a.toValue = [NSNumber numberWithFloat:-M_PI*2];
[self.reloadButton.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
a.duration = 2.0; // seconds
a.repeatCount = HUGE_VAL;
[self.reloadButton.layer addAnimation:a forKey:nil];
Your code works fine for me as long as I set reloadButton.wantsLayer = YES;
From Enabling Core Animation Support in Your App
In iOS apps, Core Animation is always enabled and every view is backed by a layer. In OS X, apps must explicitly enable Core Animation support by doing the following:
Link against the QuartzCore framework. (iOS apps must link against
this framework only if they use Core Animation interfaces
explicitly.)
Enable layer support for one or more of your NSView objects
Try this
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotation.fromValue = [NSNumber numberWithFloat:0];
rotation.toValue = [NSNumber numberWithFloat:(M_PI)];
rotation.duration = 0.2; // Speed
rotation.repeatCount = 1; // Repeat forever. Can be a finite number.
[yourButton.layer addAnimation:rotation forKey:#"Spin"];
[yourButton.layer setZPosition:100];
yourButton.transform = CGAffineTransformMakeRotation(M_PI_2);
You can change/set duration, repeatCount and toValue to your convenient.
[EDIT1]
After seeing that this is for an NS versus a UI button, there are a couple of options:
1) Use an NSTimer and the proper rotation routine for you OSX version (see the link below)
http://digerati-illuminatus.blogspot.com/2009/09/how-do-you-rotate-nsbutton-nstextfield.html
2) If you are using OSX 10.5 or above CoreAnimation was supported, and the below should actually be supported for NSButtons.
Wiki Link
http://en.wikipedia.org/wiki/Core_Animation
[ORIGINAL]
Try this code instead:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:5000000];
CGAffineTransform rr=CGAffineTransformMakeRotation(5000000);
reloadButton.transform=CGAffineTransformConcat(reloadButton.transform, rr);
[UIView commitAnimations];
Here is a link to a SO question that shows both methods
UIView Rotation