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!");
}];
Related
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];
I'm getting some weird bugs. I thought I had a handle on this. I copied code from one of my other projects. I'm wondering if it could be something to do with my custom class.
I have a view controller initialize my custom class called "TitleCardView"
Inside there I have a bunch of animations like this one:
CGPoint startPointb = borderMaskLayer.position;
CGPoint endPointb = CGPointMake(borderMaskLayer.position.x, borderMaskLayer.position.y-1000);
CABasicAnimation* bmoveAnim = [CABasicAnimation animationWithKeyPath:#"position"];
bmoveAnim.delegate=self;
[bmoveAnim setValue:#"borderMaskAnim1" forKey:#"id"];
bmoveAnim.fromValue = [NSValue valueWithCGPoint:startPointb];
bmoveAnim.toValue = [NSValue valueWithCGPoint:endPointb];
bmoveAnim.duration = 1;
[bmoveAnim setBeginTime:CACurrentMediaTime()+.4];
bmoveAnim.fillMode = kCAFillModeForwards;
bmoveAnim.removedOnCompletion = NO;
bmoveAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[self.borderLayer.mask addAnimation:bmoveAnim forKey:#"position"];
[self.borderWhiteLayer.mask addAnimation:bmoveAnim forKey:#"position"];
The animation works fine but when I try to implement AnimationDidStop{, as soon as the view loads, all the animations get logged by the delegate method with the FALSE (did not finish) flag.
I added a button and tried to use:
[UIView animateWithDuration:6 animations:^{
continueButton.alpha = 1.0f;
}];
and this code with the delay parameter....
Same problem. As soon as the view loads, its like the animation gets run immediately with a duration of 0.
Are you not supposed to add animations in your init method? I feel like there must be a rule I'm breaking that I don't know about.
This code does work:
this button is the last thing in the init method
continueButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[continueButton addTarget:self
action:#selector(titleNext)
forControlEvents:UIControlEventTouchUpInside];
[continueButton setTitle:#"Click to Continue" forState:UIControlStateNormal];
continueButton.frame = CGRectMake(0.0, 390.0, 320.0, 75.0);
[self addSubview:continueButton];
continueButton.alpha = 1.0;
then this is the method it calls
-(void)titleNext{
// proceeds to the motto page from the title page
[UIView animateWithDuration:.6 animations:^{
continueButton.alpha = 0.0f;
}];
}
So can anyone tell me why my animations are acting weird???
I recreated the project and found the animationDidStop delegate worked when the animations were created in -(void)viewDidAppear{ instead of -(void)viewDidLoad{
For swift it works if the animations are in
override func viewDidAppear(_ animated: Bool)
In my navigation bar, I have a magnifying glass icon that brings up a search bar. I'm not using a UISearchDisplayController, so I opted to build my own UINavigationItem and then push it over the standard UINavigationItem using pushNavigationItem.
The problem is that the UINavigationItem seems to be pushed around 8 pixels to the right. This causes the cancel button (with localized text 'Annuleren') to be really close to the edge of the screen.
I tried inspecting the self.mySearchBar.bounds at runtime, but the origin is 0,0. I've played around a bit with AutoLayout and programmatically added constraints, but I haven't been successful. I hope it's possible without AutoLayout.
This is my code:
- (IBAction)displaySearchBar:(id)sender {
if (!self.mySearchNavigationItem)
{
self.mySearchNavigationItem = [[UINavigationItem alloc] initWithTitle:#""];
self.mySearchNavigationItem.hidesBackButton = YES;
self.mySearchBar = [[UISearchBar alloc] initWithFrame:CGRectZero];
self.mySearchBar.showsCancelButton = YES;
self.mySearchBar.delegate = self;
[self.mySearchBar sizeToFit];
[self.mySearchBar setPlaceholder:#"Zoeken..."];
UIView *barWrapper = [[UIView alloc]initWithFrame:self.mySearchBar.bounds];
[barWrapper addSubview:self.mySearchBar];
self.mySearchNavigationItem.leftBarButtonItem = nil;
self.mySearchNavigationItem.backBarButtonItem = nil;
self.mySearchNavigationItem.titleView = barWrapper;
UIButton *cancelButton;
UIView *topView = self.mySearchBar.subviews[0];
for (UIView *subView in topView.subviews) {
if ([subView isKindOfClass:NSClassFromString(#"UINavigationButton")]) {
cancelButton = (UIButton*)subView;
}
}
if (cancelButton) {
[cancelButton setTitle:#"Annuleren" forState:UIControlStateNormal];
}
}
[self.navigationController.navigationBar pushNavigationItem:self.mySearchNavigationItem animated:YES];
NSTimeInterval delay;
if (self.tableView.contentOffset.y >1000) delay = 0.4;
else delay = 0.1;
[self performSelector:#selector(activateSearch) withObject:nil afterDelay:delay];
}
try:
self.navigationController.navigationBar.barTintColor = self.mySearchBar.barTintColor;
if that doesn't work, you can add an underlay view to the navigation controller that is the color you would like. this may be useful: Get the right color in iOS7 translucent navigation bar
After searching for many hours, I gave up and went for a dirty fix. I'll leave it open for a while, in case someone knows why my searchbar is moved 8 pixels to the right.
Right before showing the UINavigationItem, I move the whole UINavigationBar to x-coordinate -8.
self.navigationController.navigationBar.frame = CGRectMake(-8.0, self.navigationController.navigationBar.frame.origin.y, self.navigationController.navigationBar.frame.size.width, self.navigationController.navigationBar.frame.size.height);
[self.navigationController.navigationBar pushNavigationItem:self.mySearchNavigationItem animated:YES];
And then on the cancel button click, I move it back to x-coordinate 0.
- (IBAction)cancelSearchBar:(id)sender {
[self.navigationController.navigationBar popNavigationItemAnimated:YES];
self.navigationController.navigationBar.frame = CGRectMake(0.0, self.navigationController.navigationBar.frame.origin.y, self.navigationController.navigationBar.frame.size.width, self.navigationController.navigationBar.frame.size.height);
}
I want to create something that is basically a clone of what photosynth does for their tutorial page. A small "?" button pops up what looks like a new view in a frame that is slightly smaller than the first view, so that you can still see the first view around the edges.
It's a little tough to see from the pic above, but the part around the edges is the old view that the tutorial display popped up over.
My first guess is that I need to use a container view somehow, but I can't find anything on the web about exactly how to do this. I can currently create a container view, hook it up to a new view controller via a segue, and do whatever I want in that new view controller, but the container view is always visible on the view it is contained within. Any help?
BTW, I'm using storyboarding with ARC.
You can add a transparent view to the key window, add a tap gesture recognizer that would dismiss it and the subviews to show the content:
#define OVERLAY_TAG 997
-(void)showTutorial
{
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *overlay = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
overlay.backgroundColor = [UIColor clearColor];
overlay.userInteractionEnabled = YES;
[keyWindow addSubview:overlay];
UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(dismissTutorial)];
CGFloat border = 10;
CGRect frame = overlay.bounds;
// 20 is the status bar height (sorry for using the number)
frame = CGRectMake(border, border + 20, frame.size.width - border * 2, frame.size.height - border * 2 - 20);
// the black view in the example is probably a scroll view
UIView *blackView = [[UIView alloc] initWithFrame:frame];
blackView.backgroundColor = [UIColor blackColor];
blackView.alpha = 0.0;
[overlay addSubview:dimView];
// add all the subviews for your tutorial
// make it appear with an animation
[UIView animateWithDuration:0.3
animations:^{dimView.alpha = 1;}
completion:^(BOOL finished){[overlay addGestureRecognizer:tapRecognizer];}];
}
-(void)dismissTutorial
{
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *overlay = [keyWindow viewWithTag:OVERLAY_TAG];
[UIView animateWithDuration:0.3
animations:^{
overlay.alpha = 0.0;
}
completion:^(BOOL finished){
[overlay removeFromSuperview];
}];
}
This way you would remove the tutorial with a simple tap but you can use a button for instance.
I've started programming for iOS only for a few weeks, so I don't know how it was done before, but I'd like to use other transition styles for my segues. Like the one where the screen flips, giving you the impression that the destination view controller was on the back of the first one. I suppose I have to subclass UIStoryboardSegue, but apart from that, I have no idea where to go from there.
Thanks for your time!
You can use CATransition within a custom Segue to achieve any kind of transition. Here is sample code.
-(void)perform {
__block UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
__block UIViewController *destinationController = (UIViewController*)[self destinationViewController];
CATransition* transition = [CATransition animation];
transition.duration = .25;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
transition.subtype = kCATransitionFromLeft; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
[sourceViewController.navigationController.view.layer addAnimation:transition
forKey:kCATransition];
[sourceViewController.navigationController pushViewController:destinationController animated:NO];
}
You visit this link for more details http://blog.jambura.com/2012/07/05/custom-segue-animation-left-to-right-using-catransition/
Ok I didn't look far enough. Select the segue you want to customize, and there's a "transition" style option.