As everyone know the UINavigationController push a ViewController from Left To Right, is there a way to push the View from Right To Left? like the animation for the back button.
For now I have this:
[self.navigationController pushViewController:viewController animated:YES];
You can create a NSMutableArray from the navigationController's array of viewcontrollers and insert new viewController before the current one. Then set the viewControllers array without animation and pop back.
UIViewController *newVC = ...;
NSMutableArray *vcs = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[vcs insertObject:newVC atIndex:[vcs count]-1];
[self.navigationController setViewControllers:vcs animated:NO];
[self.navigationController popViewControllerAnimated:YES];
Please try this one
HomePageController *pageView = [[HomePageController alloc] initWithNibName:#"HomePageController_iPhone" bundle:nil];
CATransition *transition = [CATransition animation];
transition.duration = 0.45;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
transition.delegate = self;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
self.navigationController.navigationBarHidden = NO;
[self.navigationController pushViewController:pageView animated:NO];
Try this :
//Push effect in reverse way
CATransition* transition = [CATransition animation];
transition.duration = 0.75;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController pushViewController:vc animated:NO];
This code is working fine. Please try
CATransition *transition = [CATransition animation];
transition.duration = 0.3f;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionReveal;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController pushViewController:phoneServicesViewController animated:NO];
You seem to want to "pop back". This can be achieved by three methods on your UINavigationController instance:
To come back to the "root" controller:
-(NSArray *)popToRootViewControllerAnimated:(BOOL)animated
Or to come back to one of the previous controllers :
-(NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
Or to come back to the previously pushed controller :
-(UIViewController *)popViewControllerAnimated:(BOOL)animated
No, right to left is reserved for popping viewcontrollers from the navigation stack. You can however get such an effect by animating the views yourself. Something like:
[UIView beginAnimations:#"rightToLeft" context:NULL];
CGRect newFrame = aView.frame;
newFrame.origin.x -= newFrame.size.width;
aView.frame = newFrame;
[UIView commitAnimations];
This will however not do anything to your navigation controller.
Just an improvement of the best answer here in my opinion:
UIViewController *newVC = ...;
[self.navigationController setViewControllers:#[newVC, self] animated:NO];
[self.navigationController popViewControllerAnimated:YES];
And to go back to the initial controller, just do a push to it.
Based on #felixlam 's answer, I upgraded and created a "reverse" direction navigation controller that overrides the default push / pop methods to act like this.
class LeftPushNavigationController: BaseNavigationController {
override func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.isEnabled = false
}
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
// If the push is not animated, simply pass to parent
guard animated else { return super.pushViewController(viewController, animated: animated) }
// Setup back button
do {
// Hide original back button
viewController.navigationItem.setHidesBackButton(true, animated: false)
// Add new one
viewController.navigationItem.rightBarButtonItem = UIBarButtonItem(
image: #imageLiteral(resourceName: "iconBackReverse"),
style: .plain,
target: self,
action: #selector(self.pop)
)
}
// Calculate final order
let finalVCs = self.viewControllers + [viewController]
// Insert the viewController to the before-last position (so it can be popped to)
var viewControllers = self.viewControllers
viewControllers.insert(viewController, at: viewControllers.count - 1)
// Insert viewcontroller before the last one without animation
super.setViewControllers(viewControllers, animated: false)
// Pop with the animation
super.popViewController(animated: animated)
// Set the right order without animation
super.setViewControllers(finalVCs, animated: false)
}
override func popViewController(animated: Bool) -> UIViewController? {
// If the push is not animated, simply pass to parent
guard animated else { return super.popViewController(animated: animated) }
guard self.viewControllers.count > 1 else { return nil }
// Calculate final order
var finalVCs = self.viewControllers
let viewController = finalVCs.removeLast()
// Remove the parent ViewController (so it can be pushed)
var viewControllers = self.viewControllers
let parentVC = viewControllers.remove(at: viewControllers.count - 2)
// Set the viewcontrollers without the parent & without animation
super.setViewControllers(viewControllers, animated: false)
// Create push animation with the parent
super.pushViewController(parentVC, animated: animated)
// Set the right final order without animation
super.setViewControllers(finalVCs, animated: false)
// Return removed viewController
return viewController
}
#objc
private func pop() {
_ = popViewController(animated: true)
}
}
Swift 5.0
let pageView = TaskFolderListViewController.init(nibName: "HomePageController_iPhone", bundle: nil)
let transition = CATransition()
transition.duration = 0.45
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default)
transition.type = CATransitionType.push
transition.subtype = CATransitionSubtype.fromLeft
self.navigationController?.view.layer.add(transition, forKey: kCATransition)
self.navigationController?.pushViewController(pageView, animated: false)
I had the same problem, this is how I solved it
[self.navigationController popViewControllerAnimated:YES];
This will resemble "going back" i.e a situation where a back button is clicked and you navigate to the previous page
Based on previous best answers, you can make UINavigationController extension and preserve the navigation stack as follows:
extension UINavigationController {
/// Pushes view controller into navigation stack with backwards animation.
func pushBackwards(viewController newViewController: UIViewController) {
if let currentController = viewControllers.last {
let previousControllers = viewControllers[0..<viewControllers.endIndex-1]
var controllers = Array(previousControllers + [newViewController, currentController])
setViewControllers(controllers, animated: false)
popViewController(animated: true)
// Adjusting the navigation stack
_ = controllers.popLast()
controllers.insert(currentController, at: controllers.count-1)
setViewControllers(controllers, animated: false)
} else {
// If there is no root view controller, set current one without animation.
setViewControllers([newViewController], animated: false)
}
}
}
Related
I have developed an app for iOS7 and now trying to update it for iOS8.
Issue i have is the following:
The app screen orientation can be rotated and a few buttons in some cases move drastically. I have a few popovers that point to these buttons, so if a popover is open when screen rotates, button moves, i need the popover to also.
In iOS7 i did this by the following:
When screen rotated i updated the constraints
- (void) updateViewConstraints
{
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
self.Button.constant = (CGFloat)10;
}
else
{
self.Button.constant = (CGFloat)5;
}
[super updateViewConstraints];
}
I also move the popover
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
if(TempDisplayPopoverController == examplePopoverController)
{
[examplePopoverController presentPopoverFromRect:[self ExamplePopoverPosition] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
I initially load the popover
- (void) LoadPopover{
examplePopover = [[examplep alloc] initWithNibName:#"exampleP" bundle:nil];
[examplePopover setDelegate:self];
examplePopoverController = [[UIPopoverController alloc] initWithContentViewController: examplePopover];
[examplePopoverController setDelegate:self];
examplePopoverController.popoverContentSize = examplePopover.view.frame.size;
TempDisplayPopoverController = examplePopoverController;
if ([examplePopoverController isPopoverVisible])
{
[examplePopoverController dismissPopoverAnimated:YES];
}
else
{
[examplePopoverController presentPopoverFromRect:[self ExamplePopoverPosition] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
[self ExamplePopoverPosition] just returns button position.
This all worked fine, i was happy, iPad was happy and all behaved.
Now due to iOS8 i have to change a few bits.
self.interfaceOrientation is depreciated
[examplePopoverController presentPopoverFromRect:[self ExamplePopoverPosition] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
in didRotateFromInterfaceOrientation throws an error
"Application tried to represent an active popover presentation: <UIPopoverPresentationController: 0x7bf59280>"
I've managed to rectify self.interfaceOrientation by
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self SetUpScreen:toInterfaceOrientation];
}
- (void) SetUpScreen:(UIInterfaceOrientation)toInterfaceOrientation{
if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
self.Button.constant = (CGFloat)10;
}
else
{
self.Button.constant = (CGFloat)5;
}
[super updateViewConstraints];
}
but have no idea how to resolve the popover issue. I have tried
popoverController: willRepositionPopoverToRect: inView:
but just can't to seem to get it to work.
Can anyone advice
Thanks
In iOS 8 you can use -viewWillTransitionToSize:withTransitionCoordinator: to handle screen size (and orientation) changes:
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[_popover dismissPopoverAnimated:NO];
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// Update your layout for the new size, if necessary.
// Compare size.width and size.height to see if you're in landscape or portrait.
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[_popover presentPopoverFromRect:[self popoverFrame]
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:NO];
}];
}
When you implement this method, the deprecated rotation methods like willAnimateRotationToInterfaceOrientation: will not be called when running on iOS 8.
When using popoverController:willRepositionPopoverToRect:inView:, when reassigning to the rect parameter, try using:
*rect = myNewRect;
and not:
rect = &presentingRect;
Also, make sure you have properly assigned the popover controller's delegate.
First, you don't need to dismiss and present the popover on rotation. UIPopoverPresentationController does that for you. You don't even need to update sourceView/sourceRect once they are set on creating the popover.
Now, the trick with animate(alongsideTransition: ((UIViewControllerTransitionCoordinatorContext) -> Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil) is that you should update your constraints in alongsideTransition closure, not in completion. This way you ensure that UIPopoverPresentationController has the updated sourceRect when restoring the popover at the end of rotation.
What might seem counter-intuitive is that inside alongsideTransition closure you already have your new layout that you derive your constraints calculation from.
Here's an example in Swift:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
if self.popover != nil {
// optionally scroll to popover source rect, if inside scroll view
let rect = ...
self.scrollView.scrollRectToVisible(rect, animated: false)
// update source rect constraints
myConstraint.constant = ...
myConstrainedView.setNeedsLayout()
myConstrainedView.layoutIfNeeded()
}
}, completion: nil)
}
Very interesting - I got this to work without updating the position manually. I don't know why this works though.
let buttonContainer = UIView(frame: CGRectMake(0, 0, 44, 44))
let button = UIButton(frame: CGRectMake(0, 0, 44, 44))
buttonContainer.addSubview(button)
view.addSubview(buttonContainer)
popover!.presentPopoverFromRect(button, inView: button.superview!, permittedArrowDirections: .Any, animated: true)
Put the button that the popover is presenting from inside a "container view". Then the popover will automatically adjust location upon orientation change.
I am implementing a custom interactive navigation controller transition to "push" a new view controller by panning downwards on the navigation controller's view.
Everything works great, except in situations where (in efforts to break the app), I pan down again on the navigation controller's view to initiate a new interactive pushing of a view controller. If I do it quick enough after just finishing the previous one, I get a warning
"nested push animation can result in corrupted navigation bar"
And eventually as I continue to play with the app, I will get:
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
I understand the for whatever reason, the context is not back in correct state, and there is something weird probably happening in the viewDidAppear/viewWillAppear methods in the pushed controller, but I'm not able to narrow down where exactly to stop the pushing of a new view controller before making sure the last view controller has FOR SURE finished being pushed.
I've played around with transition coordinator's notifyWhenInteractionEndsUsingBlock, by disabling the pan gesture until this block gets called, but this has not helped me.
I've searched on SO for these warnings, but it doesn't seem to apply to my situation - whereas I feel like its something to do with the context not being managed properly.
Here is the code I'm using:
I set a delegate to handle the pan gesture's begin state by pushing the view controller onto the stack:
Application.h
-(void)transitionManagerDidBeginDraggingDown:(TransitionManager *)transitionManager{
MenuViewController *menu = [[MenuViewController alloc] initWithNibName:#"MenuViewController" bundle:nil];
[self.navigationController pushViewController:menu animated:YES];
}
This method below is what initiates the delegate's pushing of the view controller onto the stack:
TransitionManage.h
- (void)panned:(UIPanGestureRecognizer*)gesture{
UIViewController *toVc = [self.context viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromVc = [self.context viewControllerForKey:UITransitionContextFromViewControllerKey];
switch (gesture.state) {
case UIGestureRecognizerStateBegan:{
if (self.interactiveTransitionUnderway == NO) {
self.interactive = YES;
CGPoint velocity = [gesture velocityInView:gesture.view];
if (velocity.y < 0) { // we are pulling upwards on the visible view
self.presenting = YES;
[self.delegate transitionManagerDidBeginDraggingUp:self];
}
else{ // we are pulling downwards on the visible view
self.presenting = NO;
[self.delegate transitionManagerDidBeginDraggingDown:self];
}
}
break;
}
case UIGestureRecognizerStateChanged:{
CGPoint touchLocation = [gesture locationInView:gesture.view];
CGPoint translation = [gesture translationInView:gesture.view];
CGPoint updatedCenter = CGPointMake(self.beginPoint.x, self.beginPoint.y + translation.y);
toVc.view.center = updatedCenter;
CGFloat d = fabs(touchLocation.y / CGRectGetHeight(self.parentViewController.view.bounds)) ;
[self.context updateInteractiveTransition:d];
break;
}
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateEnded:{
CGPoint velocity = [gesture velocityInView:gesture.view];
CGRect frame;
if (velocity.y < 0)
frame = CGRectMake(0, -toVc.view.frame.size.height, toVc.view.frame.size.width, toVc.view.frame.size.height); // pulling up
else
frame = CGRectMake(0, 0, toVc.view.frame.size.width, toVc.view.frame.size.height); // pulling down
[UIView animateWithDuration:0.75 delay:0.0 usingSpringWithDamping:0.8 initialSpringVelocity:0.0
options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveEaseOut animations:^{
toVc.view.frame = frame;
} completion:^(BOOL finished) {
if (velocity.y < 0){
[self cancelInteractiveTransition];
}
else{
[self finishInteractiveTransition];
UIView *snapshotView = [fromVc.view snapshotViewAfterScreenUpdates:YES];
[toVc.view addSubview:snapshotView];
[toVc.view sendSubviewToBack:snapshotView];
}
[self completeTransition];
}];
break;
}
default:
break;
}
}
- (void)completeTransition{
BOOL finished = ![self.context transitionWasCancelled];
[self.context completeTransition:finished];
}
- (void)animationEnded:(BOOL)transitionCompleted {
// Reset to our default state
self.interactive = NO;
self.presenting = NO;
self.context = nil;
self.interactiveTransitionUnderway = NO;
}
I do not have a full answer to your issue but at least you should prevent your code from pushing a new viewcontroller with animation before the previous view controller is fully pushed (animation finished).
I have one UIViewController in which another viewcontroller(1) and splitviewcontroller(2) do switching via addchildviewcontroller method. So when I add splitviewcontroller it doesn't handle correct the rotations. Look at the video – https://dl.dropbox.com/u/2139277/IMG_0180.MOV.
Here is the code that does switching:
- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
if (fromViewController == toViewController)
return;
// animation setup
toViewController.view.frame = self.view.bounds;
// notify
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
// select animation direction
UIViewAnimationOptions animation = (_contentState == ContentStateViewingMap) ? UIViewAnimationOptionTransitionCurlUp : UIViewAnimationOptionTransitionCurlDown;
// transition
ContentState previousState = _contentState;
_contentState = ContentStateAnimating;
[self transitionFromViewController:fromViewController
toViewController:toViewController
duration:0.6
options:animation | UIViewAnimationOptionCurveEaseInOut
animations:nil
completion:^(BOOL finished) {
[toViewController didMoveToParentViewController:self];
[fromViewController removeFromParentViewController];
_contentState = (previousState == ContentStateViewingMap) ? ContentStateViewingList : ContentStateViewingMap;
}];
}
AFAIK a UISplitViewController MUST be the root view controller of the window. Adding it as child view controller is not supported.
I use a bunch of custom segues to segue from one view controller view to another using:
[self performSegueWithIdentifier:#"createTeamAccountSegue" sender:self];
My question is, do the previous views automatically get destroyed in iOS5 and 6?
I keep having to create custom segues to go to the next view, and then create yet another new segue animation in reverse to go back to the last view. As long as they dont keep stacking up and up then this should be fine right?
EDIT: I should probably show you the general layout of what my custom UIStoryboardSegue class looks like:
- (void) perform {
UIViewController *sourceViewController = (UIViewController *) self.sourceViewController;
UIViewController *destinationViewController = (UIViewController *) self.destinationViewController;
UIView *parent = sourceViewController.view.superview;
[parent addSubview:destinationViewController.view];
[parent sendSubviewToBack:destinationViewController.view];
sourceViewController.view.layer.masksToBounds = NO;
sourceViewController.view.layer.cornerRadius = 8; // if you like rounded corners
sourceViewController.view.layer.shadowOffset = CGSizeMake(0,0);
sourceViewController.view.layer.shadowRadius = 10;
sourceViewController.view.layer.shadowOpacity = 1;
destinationViewController.view.frame = CGRectMake(0, 20, destinationViewController.view.frame.size.width, destinationViewController.view.frame.size.height);
sourceViewController.view.frame = CGRectMake(0, 20, sourceViewController.view.frame.size.width, sourceViewController.view.frame.size.height);
[UIView animateWithDuration:.3 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^
{
sourceViewController.view.frame = CGRectMake(0, parent.frame.size.height, sourceViewController.view.frame.size.width, sourceViewController.view.frame.size.height);
} completion:^(BOOL finished)
{
[sourceViewController presentViewController:destinationViewController animated:NO completion:NULL];
}];
}
If you are doing there segues in a UINavigationController then no, they aren't destroyed. To go back to one you should use [self.navigationController popViewControllerAnimated:YES];
Is it possible to add QLPreviewController to UIView as sub view.
I tried like this
[self.view addSubview:previewViewController.view]
I also called reloadData
[previewViewController reloadData];
I check with this URL Adding QLPreviewController as subview doesn't load PDF . But I did not understand what is self.pdfPreviewView
Please guide me how I can add QLPreviewController as sub view..
Yes its possible, see the code below:
QLPreviewController* preview = [[QLPreviewController alloc] init];
preview.dataSource = self;
preview.delegate = self;
[self addChildViewController:preview];//*view controller containment
//set the frame from the parent view
CGFloat w= self.quickLookView.frame.size.width;
CGFloat h= self.quickLookView.frame.size.height;
preview.view.frame = CGRectMake(0, 0,w, h);
[self.quickLookView addSubview:preview.view];
[preview didMoveToParentViewController:self];
//save a reference to the preview controller in an ivar
self.previewController = preview;
Swift 3.x
private var pVC: QLPreviewController?
override func viewDidLoad() {
super.viewDidLoad()
// I do not not why, but it needs to be setup after delay.
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: setupPreview)
}
private func setupPreview() {
if (pVC != nil) { return }
let preview = QLPreviewController()
preview.dataSource = self
preview.delegate = self
preview.view.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: previewView.frame.size)
previewView.addSubview(preview.view)
preview.didMove(toParentViewController: self)
pVC = preview
}