CALayer Animation - objective-c

I'm a beginner and I trying continue to familiarize myself with CALayer ...
Thanks again #Alexsander Akers, #DHamrick and #Tommy because now I can skew my CALayer !
It look like :
I would like to move my finger on the Gray UIView (with touchesBegan/touchesMoved/touchesEnded) and my "cards" move like this :
(For exemple if I move my finger left)
the yellow card disappear & the green one take is place
white take the green's place
red the white
blue the red one
and instead of a blue card a Black one appear ...
Maybe I'm dreaming and it's to hard for me, but if if you can give me advice I'll take it !!
thank you !

You may use the customized function like this
- (void)moveAndRotateLayer: (CALayer *)layer withDuration:(double)duration degreeX:(double)degreeX y:(int)degreeY angle:(float)angle autoreverseEnable:(BOOL)ar repeatTime:(int)repeat andTimingFunctionType:(NSString *)type
{
// You should type the timing function type as "Liner", "EaseIn", "EaseOut" or "EaseInOut".
// The default option is the liner timing function.
// About these functions, look in the Apple's reference documents.
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
CGPoint movement = CGPointMake(layer.position.x + degreeX, layer.position.y + degreeY);
NSArray *animations = [NSArray arrayWithObjects:[self moveLayers:layer to:movement duration:duration], [self rotateLayers:layer to:angle duration:duration], nil];
theGroup.duration = duration;
theGroup.repeatCount = repeat;
theGroup.autoreverses = ar;
if ([type isEqualToString:#"EaseIn"])
{
theGroup.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn];
}
else if ([type isEqualToString:#"EaseOut"])
{
theGroup.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
}
else if ([type isEqualToString:#"EaseInOut"])
{
theGroup.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
}
else
{
theGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
}
theGroup.animations = [NSArray arrayWithArray:animations];
[layer addAnimation:theGroup forKey:#"movingAndRotating"];
}
This is the solution of the animation just for moving and rotating the layer,
but, I know that you can customizing yourself.
It's very good for you. You can make it.
Good luck!

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];

IOS Tab Bar height issue on iPhone X

I have tried many solutions including This Solution. So Please read full question before flagging it as duplicate. For iPhone X I have use Safe Area guide to support iPhone X design. Everything is fine except for Tab Bar I have double check on its safe area constraints but still after running it's items got scattered.
But please not that on height of tab bar is constant and correct only items in it are disturb.
Here is image of my story board please not its constraints and its adjustment.
As per my knowledge everything is everything is right... but for sure I am missing something, please check and any suggestion will be a great help. But please not I have tried almost every way which is mentioned on stack or first few links of google.
Here is my code which I am using to populate items' images.
UIColor *color = [UIColor colorWithRed:192.0f/255.0f green:41.0f/255.0f blue:66.0f/255.0f alpha:1.0f];
NSDictionary *textColors = [NSDictionary dictionaryWithObjectsAndKeys:color, NSForegroundColorAttributeName, nil];
for(UITabBarItem *tab in self.tabBar.items)
{
[tab setTitleTextAttributes:textColors forState:UIControlStateNormal];
[tab setTitleTextAttributes:textColors forState:UIControlStateSelected];
}
self.tabBar.itemPositioning = UITabBarItemPositioningAutomatic;
[self.tabBar.items[0] setImage:[[UIImage imageNamed:#"search_icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
[self.tabBar.items[1] setImage:[[UIImage imageNamed:#"user_icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
[self.tabBar.items[2] setImage:[[UIImage imageNamed:#"call_icon"]
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
[self.tabBar.items[3] setImage:[[UIImage imageNamed:#"services_icon"]
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
at first I tried almost every possible solution but that didn't work at all, but then at the end somehow I added some negative margin with bottom. Let's say "-1" and then everything started working like charm. I also tried setting positive margin just for experiment. But that didn't work, I concluded only negative number works. This must be an issue in UIKit.
Set bottom constraint with safe area as any negative number will solve this issue.
check its bottom constraint.
Try using the method recommended in the documentation to set the items.
To configure tab bar items directly, use the setItems:animated: method
of the tab bar itself.
- (void)setItems:(NSArray<UITabBarItem *> *)items animated:(BOOL)animated;
Do something like this.
UIIImage *image0 = [UIImage imageNamed:#"search_icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
UITabBarItem *item0 = [[UITabBarItem alloc] initWithTitle:self.tabBar.items[0].title image:image0 tag:0];
// And so on or in a loop
NSArray<UITabBarItem *> *items = #[ item0, ]; // Add all items here
[self.tabBar setItems:items animated:NO];
Read more here
https://developer.apple.com/documentation/uikit/uitabbar?language=objc
Just put your UITabBar inside a UIView!
This is working for me.
This will definitely work.
override func viewDidLoad() {
super.viewDidLoad()
let numberOfItems = CGFloat(tabBArView.items!.count)
let tabBarItemSize = CGSize(width: tabBArView.frame.width / numberOfItems, height: 48)
tabBArView.selectionIndicatorImage = UIImage.imageWithColor(color: UIColor.orange, size: tabBarItemSize).resizableImage(withCapInsets: UIEdgeInsets.zero)
tabBArView.frame.size.width = self.view.frame.width + 4
tabBArView.frame.origin.x = -2
extension UIImage {
class func imageWithColor(color: UIColor, size: CGSize) -> UIImage {
let rect: CGRect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}

Child SkShapeNode positioning confusion

I've written this code for the game over scene I have for a game:
#import "GameOverScene.h"
#import "SharedInfo.h"
#implementation GameOverScene
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
[self setupView];
}
-(void)showGameEndingWithGameInformation:(NSDictionary *)gameEndingInformation{
}
-(void)setupView{
SKLabelNode *GOTitle = [SKLabelNode labelNodeWithFontNamed:#"Generica Bold"];
GOTitle.fontSize = 40.f;
NSString* text = [NSString stringWithFormat:#"GAME OVER"];
[GOTitle setText:text];
GOTitle.position = CGPointMake(CGRectGetMidX(self.frame), self.frame.size.height- GOTitle.frame.size.height*1.5);
[GOTitle setFontColor:[[SharedInfo sharedManager]colorFromHexString:#"#2EB187"]];
[self addChild: GOTitle];
SKLabelNode *replayButton = [SKLabelNode labelNodeWithFontNamed:#"Quicksand-Bold"];
replayButton.fontSize = 25.f;
NSString* replayText = [NSString stringWithFormat:#"Play Again"];
[replayButton setText:replayText];
replayButton.name = kGOSceneReplayButton;
replayButton.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)- self.frame.size.height/5);
[replayButton setFontColor:[SKColor whiteColor]];
SKShapeNode *bgNode = [SKShapeNode shapeNodeWithRectOfSize:replayButton.frame.size];
[bgNode setFillColor:[UIColor redColor]];
[replayButton addChild:bgNode];
[self addChild:replayButton];
NSLog(#"replay dimensions: %#",NSStringFromCGRect(replayButton.frame));
SKLabelNode *returnButton = [SKLabelNode labelNodeWithFontNamed:#"Quicksand-Bold"];
returnButton.fontSize = 25.f;
NSString* returnText = [NSString stringWithFormat:#"Return To Main Menu"];
[returnButton setText:returnText];
returnButton.name = kGOSceneReturnToMainButton;
returnButton.position = CGPointMake(CGRectGetMidX(self.frame), replayButton.position.y -self.frame.size.height/7 );
[returnButton setFontColor:[SKColor whiteColor]];
[self addChild:returnButton];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *sprite = [self nodeAtPoint:location];
NSLog(#"sprite name: %#",sprite.name);
if ([sprite.name isEqualToString:kGOSceneReturnToMainButton]||[sprite.name isEqualToString:kGOSceneReturnToMainButton]) {
//return to main menu or retry
[self.gameEndingSceneDelegate goToScene:sprite.name withOptions:nil]; //Sort out the options later on.
}
}
#end
When I run it though, I get this:
There are two issues I'm really confused about. Firstly, why do I have 8 nodes in the scene, where I should really have 4? I think something is doubling the nodes, but that's just a guess.
The more confusing issue is the red SKShapeNode positioning. I've read that scaling the parent node can cause problems to the child SKShapeNode, but I'm not scaling anything. Also, why does it place my red rectangle at a random position (it's not the middle of the parent, or corresponding with the bottom).
Thanks a lot for all the help in advance.
UPDATE 1: So following the suggestion, I checked if my method is being called twice, and thus creating the duplicates. No luck there, as it is only called once. The mystery still going strong!
As for the positioning shenanigans, I changed the code slightly to set the position of the red rectangle to match its parent node:
SKLabelNode *replayButton = [SKLabelNode labelNodeWithFontNamed:#"Quicksand-Bold"];
replayButton.fontSize = 25.f;
NSString* replayText = [NSString stringWithFormat:#"Play Again"];
[replayButton setText:replayText];
replayButton.name = kGOSceneReplayButton;
replayButton.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)- self.frame.size.height/5);
[replayButton setFontColor:[SKColor whiteColor]];
SKShapeNode *bgNode = [SKShapeNode shapeNodeWithRectOfSize:replayButton.frame.size];
[bgNode setFillColor:[UIColor redColor]];
[self addChild:replayButton];
bgNode.position = replayButton.position;
[replayButton addChild:bgNode];
But after updating, I got this:
In case it helps, this is what I do to present the scene:
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = YES;
scene = [GameOverScene sceneWithSize:self.view.frame.size];
[(GameOverScene*)scene setGameEndingSceneDelegate:self];
[(GameOverScene*)scene setScaleMode: SKSceneScaleModeAspectFill];
[(GameOverScene*)scene showGameEndingWithGameInformation:self.gameEndingInfo];
// Present the scene.
[skView presentScene:scene transition:sceneTransition];
Also, this is the output of my NSLog:
replay dimensions: {{221, 91}, {127, 25}
I've got a feeling that because I set my scene's setScaleMode, it gets strange, but nothing else is out of ordinary, so not sure what to do. I'm thinking maybe just create an image for my label and change the SKLabelNode to SKSpriteNode and set the image, so I skip adding the red rectangle as background for the label node. The reason I wanted to add the rectangle is actually to provide bigger hit target for when the 'Button' is tapped, so if anyone knows an easier, more straightforward way, I'd really appreciate it.
UPDATE 3:
I also tried setting the position of the rectangle to match that of parent label node:
bgNode.position = [replayButton convertPoint:CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)- self.frame.size.height/5) fromNode:self];
the rectangle ends up at the same place as the last update (all the way to the right of the screen)
There are few issues with your code:
lineWidth property and it's default value of 1.0. It should be 0.0f
verticalAlignmentMode property and it's default baseline alignment. It should be SKLabelVerticalAlignmentModeCenter.
Wrong positioning of a shape node. It should be (0,0)
To fix it, change label's vertical alignment:
replayButton.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
set shapenode's lineWidth property to 0.0f:
bgNode.lineWidth = 0.0f;
and remove this line:
//bgNode.position should be CGPointZero which is (0,0)
bgNode.position = replayButton.position;
Still, I would stay away of this approach. SKShapeNode is not needed in this situation. You can do the same with SKSpriteNode. What is important is that both SKShapeNode and SKLabelNode can't be drawn in batches, which means, can't be drawn in a single draw pass when rendered like SKSpriteNode. Take a look at this. Your example is too simple to make performance issues, but in general you should keep all this in mind.
If your button's text never change during the game, you should consider using SKSpriteNode initialized with texture. If interested in a pre made buttons for SpriteKit, take a look at SKAButton.
Hope this helps!

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?

Force NavigationBar's title to animate

I'm developing a navigation system that drills down three steps and then lets you navigate the contents with arrows that actually just change the contents of that view, so technically the navigationcontroller doesn't receive any pop/push, because of that I as soon as I do a
self.navigationItem.title = [[[self.symbol valueForKey:#"section_title"] uppercaseString] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
the title changes as It should but for obvious reasons doen't animate.
Is there a way to force it to animale as if a pop/push action happened?
something like:
self.navigationItem.direction = NavigationDirectionLeft;
self.navigationItem.title = #"Whatevah!";
so that as soon as the title changes it make the old title fades out
in a transition that goes from left to right and the the title fades in, or
way better the new title faded in transitioning from left to right entering
from the left side
[FYI I don't need to support ios versions prior to 5.0, so only 5.0 > :)]
OK, this isn't a perfect solution, but might give you a starting point:
Fading between nav bar titles is fairly simple:
CATransition *fade = [CATransition animation];
fade.type = kCATransitionFade;
fade.duration = 2.0;
[self.navigationController.navigationBar.layer addAnimation: fade forKey: #"fadeText"];
self.navigationItem.title = "new Title";
There are other kinds of "out of the box" transitions you can use to animate the position of the title, so for example:
CATransition *push = [CATransition animation];
push.duration = 2.0;
push.type = kCATransitionPush;
push.subtype = kCATransitionFromRight;
[self.navigationController.navigationBar.layer addAnimation: push forKey: #"pushText"];
self.navigationItem.title = "new Title";
The problem with this is that the whole nav bar moves. To get around this you'll need to find the layer for the title label in the navbar's subviews. This view hierarchy is 'private', so you shouldn't go submitting this code to the store, but like I say… this might give you a starting point.
CATransition *fade = [CATransition animation];
fade.type = kCATransitionFade;
fade.duration = 2.0;
CATransition *move = [CATransition animation];
move.duration = 2.0;
move.type = kCATransitionMoveIn;
move.subtype = kCATransitionFromRight;
UILabel * navBarTitleLabel;
for (UIView * view in self.navigationController.navigationBar.subviews) {
if ([NSStringFromClass(view.class) isEqualToString: #"UINavigationItemView"]) {
navBarTitleLabel = view.subviews.firstObject;
break;
}
}
[navBarTitleLabel.layer addAnimation: fade forKey: #"fadeText"];
[navBarTitleLabel.layer addAnimation: move forKey: #"moveText"];
self.navigationItem.title = newTitle;
You might find that you need to removeAllAnimations from the layer once they're complete. I didn't get a chance to fully test it all out.
Try setting a UIView as self.navigationItem.titleView and then create a UILabel and add it as title. When you want to animate, animate this Label over this UIView and add a new UILabel and animate that to replace this. You can use some block based animations for this.
For eg:-
[UIView animateWithDuration:0.5
delay:1.0
options: UIViewAnimationCurveEaseOut
animations:^{
label.frame = labelFrame;
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];