I want to change to another tab but i also want to open it from bottom. Like a vertical transition.
I was trying to do like this :
-(void)test{
UIView * toView = [[self.tabBarController.viewControllers objectAtIndex:1] view];
UIImage *img=[self imageWithView:toView];
[UIView transitionWithView:img
duration:1.0f
options:UIViewAnimationOptionCurveEaseIn
animations:^{
self.tabBarController.selectedIndex = 0;
} completion:NULL];
}
- (BOOL)tabBarController:(UITabBarController *)tabBarController
shouldSelectViewController:(UIViewController *)viewController{
return false;
}
- (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
but it's not the effect that i want . Any ideea?
This is my solution:
Firstly. return false in the UITabBarController' delegate. So the system won't change the tab immediately when user selecting the tab.
- (BOOL)tabBarController:(UITabBarController *)tabBarController
shouldSelectViewController:(UIViewController *)viewController
Secondly. Capture your toView to an image
Thirdly. Do any animation you want with the imageView.
Finally. Set selectedIndex of your tabBarController and remove the imageView when the animation completed.
Feel free to ask me. Thanks!
Related
I have self view with image view:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
[self setBluredImageView:imageView];
[self addSubview:imageView];
- (UIImage *)takeSnapshotOfView:(UIView *)view
{
CGFloat reductionFactor = 1;
UIGraphicsBeginImageContext(CGSizeMake(view.frame.size.width/reductionFactor, view.frame.size.height/reductionFactor));
[view drawViewHierarchyInRect:CGRectMake(0, 0, view.frame.size.width/reductionFactor, view.frame.size.height/reductionFactor) afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
In method when I want to show my self view over other view I make this:
- (void)showMe; {
AppDelegate* app = [AppDelegate shared];
[app.window addSubview:self];
UIImage *image = [self blurWithImageEffects:[self takeSnapshotOfView:app.window]];
[[self bluredImageView] setImage:image];
[UIView animateWithDuration:0.4 animations:^{
[self setAlpha:1.0];
}];
}
So as you can see I want to blur "graphic context" that based on main window view. At first time when I present my self view it works perfect, but then, something like blurred image multiply each other.
So this is image when I just first show my view:
When I present my view few times the blurred image looks like this:
So as you can see each time blurred screenshot is different, but I use the same method for getting screenshot and don't update content of the view controller or other ui parts.
Some methods and image categories found here.
You've created a loop of blurring. When you take the screenshot of the view a second time, the bluredImageView is also in the screenshot. That is why you see the effect multiplied. Try removing it and only capturing the context without the effect, then adding it back
- (UIImage *)takeSnapshotOfView:(UIView *)view
{
//Remove the blured image before taking another screenshot.
[self bluredImageView] removeFromSuperview];
CGFloat reductionFactor = 1;
UIGraphicsBeginImageContext(CGSizeMake(view.frame.size.width/reductionFactor, view.frame.size.height/reductionFactor));
[view drawViewHierarchyInRect:CGRectMake(0, 0, view.frame.size.width/reductionFactor, view.frame.size.height/reductionFactor) afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//Add it back now that the effect is done
[self addSubview:[self bluredImageView];
return image;
}
I've think about how to make the tabBar 's hidden animation more elegant and smoothly:
Here is how I implement:
So I just want to improve the animation, while the tabBar is suddenly, you know, disappear and hidden.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self.tabBarController.tabBar setHidden:YES];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self.tabBarController.tabBar setHidden:NO];
}
Any suggestion?
Try adding this method:
- (void)setTabBarHidden:(BOOL)tabBarHidden animated:(BOOL)animated
{
if (tabBarHidden == _isTabBarHidden)
return;
CGFloat offset = tabBarHidden ? self.tabBarController.tabBar.frame.size.height : -self.tabBarController.tabBar.frame.size.height;
[UIView animateWithDuration:animated ? 0.6 : 0.0
delay:0
usingSpringWithDamping:0.7
initialSpringVelocity:0.5
options:UIViewAnimationOptionCurveEaseIn|UIViewAnimationOptionLayoutSubviews
animations:^{
self.tabBarController.tabBar.center = CGPointMake(self.tabBarController.tabBar.center.x,
self.tabBarController.tabBar.center.y + offset);
}
completion:nil];
_isTabBarHidden = tabBarHidden;
}
Then you can call it like [self setTabBarHidden:YES animated:YES] and [self setTabBarHidden:NO animated:YES] to hide and show your bar, this will move it in and out of the screen instead of just make it instantly dissapear.
Don't forget to add a new bool property isTabBarHidden and also you can play with the values of the animation.
I have the following view hierarchy:
Tab Bar Controller -> Navigation Controller -> Custom View Controller
In my Custom View I want the TabBar to disappear and show a toolbar instead. Much like in iOS7 native photos app when pressing 'select'.
I tried different solutions I found of SO but managed to get either:
TabBar hidden and Toolbar shown with black gap
TabBar hidden and Toolbar hidden
TabBar hidden Toolbar shown with gap from bottom. However, Custom view content reaches the bottom of the screen (under the toolbar and in the same place the tab bar used to be)
The difference from other solutions I found is that I need this to happen on click and not on push.
Some of the things I tried:
// #1
[self.navigationController.toolbar setHidden:!isSelecting];
[self.tabBarController.tabBar setHidden:isSelecting];
// #2
self.hidesBottomBarWhenPushed = YES;
// #3
#1 & #2 variants # different controller along the path
Eventually, after playing with the settings I managed to make it work. I'm not sure why it works now and didn't work before so I'd appreciate your comments.
Storyboard:
Mark as checked "Hide Bottom Bar on Push" for the Custom View Controller
Mark as checked "Show Toolbar" for the Navigation Controller
Code:
On button click hide/unhide tabBar: [self.tabBarController.tabBar setHidden:state]
This almost works. It does hide/unhide the tabBar when pressing the button but the only problem is that the tabBar is initially hidden when switching tabs. I had to do some extra effort to have it visible.
Set UITabBarControllerDelegate to unhide tabBar when switching tabs. I did it in a custom SUSourceTabController:
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
}
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController: (UIViewController *)viewController
{
[self.tabBar setHidden:NO];
}
We also need to unhide it for the first tab view in the Custom View Controller code. Using setHidden:NO in any other place in the code didn't work.
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tabBarController.tabBar setHidden:NO];
}
Check this category from this question's answer.
UITabBarController+HideTabbar.h
#import <UIKit/UIKit.h>
#interface UITabBarController (HideTabbar)
- (void)setHidden:(BOOL)hidden animated:(BOOL)animated;
#end
UITabBarController+HideTabbar.m
#import "UITabBarController+HideTabbar.h"
#define kAnimationDuration .3
#implementation UITabBarController (HideTabbar)
- (void)setHidden:(BOOL)hidden animated:(BOOL)animated
{
CGRect screenRect = [[UIScreen mainScreen] bounds];
float fHeight = screenRect.size.height;
if (UIDeviceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
fHeight = screenRect.size.width;
}
if (!hidden) {
fHeight -= self.tabBar.frame.size.height;
}
CGFloat animationDuration = animated ? kAnimationDuration : 0.f;
[UIView animateWithDuration:animationDuration animations:^{
for (UIView *view in self.view.subviews){
if ([view isKindOfClass:[UITabBar class]]) {
[view setFrame:CGRectMake(view.frame.origin.x, fHeight, view.frame.size.width, view.frame.size.height)];
}
else {
if (hidden) {
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, fHeight)];
}
}
}
} completion:^(BOOL finished){
if (!hidden){
[UIView animateWithDuration:animationDuration animations:^{
for(UIView *view in self.view.subviews) {
if (![view isKindOfClass:[UITabBar class]]) {
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, fHeight)];
}
}
}];
}
}];
}
#end
I've implemented a custom (blurred) background for a modal view controller on iPad. Here's how I do it: in the presented VC's viewWillShow: I take a snapshot image of the presenting VC's view, blur it, and add a UIImage view on top of the presenting VC. This works well.
However, when I rotate the device while the modal is showing, the blurred image gets out of sync with the view controller it represents. The image gets stretched to fill the screen, but the view controller doesn't resize its views the same way. So when the modal is dismissed and the blur goes away, the transition looks bad.
I've tried taking another snapshot in didRotateFromInterfaceOrientation:, and replacing the blurred image, but that didn't work.
Any advice on how to solve this?
UPDATE: a sample project
My code (in the modal view controller):
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self drawBackgroundBlurView];
}
- (void)drawBackgroundBlurView
{
if(_blurModalBackground) {
[_backgroundBlurView removeFromSuperview];
CGRect backgroundFrame = self.presentingViewController.view.bounds;
_backgroundBlurView = [[UIImageView alloc] initWithFrame:backgroundFrame];
_backgroundBlurView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_backgroundBlurView setImage:[STSUtils imageByBlurringView:self.presentingViewController.view]];
[self.presentingViewController.view addSubview:_backgroundBlurView];
}
}
Utils code
+ (UIImage *)imageByBlurringView:(UIView *)view
{
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
BOOL isPortrait = (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]));
CGRect frame;
if(isPortrait) {
frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.height, view.frame.size.width);
}
else {
frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height);
}
UIGraphicsBeginImageContextWithOptions(frame.size, NO, window.screen.scale);
[view drawViewHierarchyInRect:frame afterScreenUpdates:NO];
// Get the snapshot
UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
// Apply blur
UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3];
UIImage *blurredSnapshotImage = [snapshotImage applyBlurWithRadius:8
tintColor:tintColor
saturationDeltaFactor:1.8
maskImage:nil];
UIGraphicsEndImageContext();
return blurredSnapshotImage;
}
I coded a simple example that takes a screenshot from presentingViewController and sets it as background image. Here is the implementaion of my modal view controller. And it works well (both on viewWillAppear and handles rotation properly). Could you please also post STSUtils code?
#import "ScreenshoterViewController.h"
#interface ScreenshoterViewController ()
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
- (IBAction)buttonBackPressed;
#end
#implementation ScreenshoterViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateImage];
}
- (void)updateImage
{
UIGraphicsBeginImageContext(self.view.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.presentingViewController.view.layer renderInContext:context];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.imageView.image = img;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[self updateImage];
}
- (IBAction)buttonBackPressed {
[self dismissViewControllerAnimated:YES
completion:NULL];
}
#end
Update
In your utils code just replace this
[view drawViewHierarchyInRect:frame afterScreenUpdates:NO];
with this
[view drawViewHierarchyInRect:frame afterScreenUpdates:YES];
From apple docs:
afterUpdates A Boolean value that indicates whether the snapshot
should be rendered after recent changes have been incorporated.
Specify the value NO if you want to render a snapshot in the view
hierarchy’s current state, which might not include recent changes.
So you were taking screenshot that did not included latest changes in the view.
I have a controller embedded in navigation controller. Let's say that i have a button that repositions self.navigationController.navigationBar a bit. Then i do presentViewControllerAnimated with any controller (doesn't matter if it's nav or not) and after dismissing it navigation bar returns to it's original position (actually it is at its original position at dismiss animation start). In iOS 6 and earlier the bar would not be repositioned automatically. Any idea how can i prevent this repositioning in iOS 7?
OK, so I finally got it right.
First of all - Apple does not want us to change position of UINavigationBar. Therefore you should avoid it at all cost. In my case i got an app to fix which moved UINavigationBar to show slide-out menu. The proper solution to slide-out menu problem is to put UINavigationController inside - then you can slide whole UINavigationController with its content (whatever it is) and everything works fine. For some reason UINavigationController was outside in this app. So, i had to resort to a hack. Do not use this method if you have ANY option not to use it. It's a hack, it might break in further iOS versions and Apple would certainly not appreciate it.
First, explore new transitioning system in iOS7: http://www.doubleencore.com/2013/09/ios-7-custom-transitions/
Then, replace:
[self presentViewController:navigationController animated:YES completion:nil];
with
if([UIApplication iOS7]) /* or any other custom iOS7 detection method you implement */
{ /* we simulate old transition with nav bar slided out */
navigationController.transitioningDelegate = [OMModalTransitionDelegate new];
}
[self presentViewController:navigationController animated:YES completion:nil];
So, we need a transition delegate to simulate standard behaviour and do the trick as well.
#import "OMModalTransitionDelegate.h"
#import "OMAnimatedTransitioning.h"
#implementation OMModalTransitionDelegate
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
OMAnimatedTransitioning *transitioning = [OMAnimatedTransitioning new];
return transitioning;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
OMAnimatedTransitioning *transitioning = [OMAnimatedTransitioning new];
transitioning.reverse = YES;
return transitioning;
}
#end
And now the actual animation manager (you have to implement sharedBar in a category on UINavigationBar yourself):
static NSTimeInterval const DEAnimatedTransitionDuration = 0.4f;
static NSTimeInterval const DEAnimatedTransitionMarcoDuration = 0.15f;
#implementation OMAnimatedTransitioning
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
UIView *superView = [UINavigationBar sharedBar].superview;
CGRect barFrame = [UINavigationBar sharedBar].frame;
if(self.reverse)
{ /* Trick - first, remove the bar from it's superview before animation starts */
[[UINavigationBar sharedBar] removeFromSuperview];
}
CGRect oldFrame = container.bounds;
if (self.reverse)
{
[container insertSubview:toViewController.view belowSubview:fromViewController.view];
}
else
{
toViewController.view.frame = oldFrame;
toViewController.view.transform = CGAffineTransformMakeTranslation(0, CGRectGetHeight(oldFrame));
[container addSubview:toViewController.view];
}
[UIView animateKeyframesWithDuration:DEAnimatedTransitionDuration delay:0 options:0 animations:^
{
if (self.reverse)
{
fromViewController.view.transform = CGAffineTransformMakeTranslation(0, CGRectGetHeight(oldFrame));
double delayInSeconds = 0.01; /* Trick - after an imperceivable delay - add it back - now it is immune to whatever Apple put there to move it */
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
[UINavigationBar sharedBar].frame = barFrame;
[superView addSubview:[UINavigationBar sharedBar]];
});
}
else
{
toViewController.view.transform = CGAffineTransformIdentity;
}
} completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return DEAnimatedTransitionDuration;
}
#end
In your custom navigation controller, add
- (void)viewWillLayoutSubviews {
//do your navigation bar layout
}
hope this can help you. Remind, above method only be supported ios >= 5.0.