How to make UIPopoverController keep same position after rotating? - objective-c

I can't keep popover the same position on the screen after rotation. Is there any good way to do that, because just setting some frame to popover works terrible after rotating.popover.frame = CGRectMake(someFrame); After rotation popover looks fine only if it is in the center of the screen.

Apple has a Q&A on exactly this issue. You can find the details here:
Technical Q&A QA1694 Handling Popover Controllers During Orientation Changes
Basically, the technique explains that in your view controller's didRotateFromInterfaceOrientation method, you will present the pop over again as follows:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[aPopover presentPopoverFromRect:targetRect.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
For more information, have a read of the article above, and also the UIPopoverController Class Reference:
If the user rotates the device while a popover is visible, the popover
controller hides the popover and then shows it again at the end of the
rotation. The popover controller attempts to position the popover
appropriately for you but you may have to present it again or hide it
altogether in some cases. For example, when displayed from a bar
button item, the popover controller automatically adjusts the position
(and potentially the size) of the popover to account for changes to
the position of the bar button item. However, if you remove the bar
button item during the rotation, or if you presented the popover from
a target rectangle in a view, the popover controller does not attempt
to reposition the popover. In those cases, you must manually hide the
popover or present it again from an appropriate new position. You can
do this in the didRotateFromInterfaceOrientation: method of the view
controller that you used to present the popover.

As of iOS 8.0.2 willRotateToInterfaceOrientation will not have any effect. As mhrrt mentioned, you need to use the delegate method:
- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view
So for example if you want your popover to appear directly below a button that was pressed, you would use the following code:
- (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view
{
CGRect rectInView = [self.theButton convertRect:self.theButton.frame toView:self.view];
*rect = CGRectMake(CGRectGetMidX(rectInView), CGRectGetMaxY(rectInView), 1, 1);
*view = self.view;
}

In iOS 7 you can use - (void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view to reposition your UIPopoverController's view on interface orientation change.
See the UIPopoverControllerDelegate documentation.

You can do this in didRotateFromInterfaceOrientation: method of the view controller that you used to present the popover.
Use setPopoverContentSize:animated: method for setting the size of the popover.

UIPopoverController was deprecated in ios9 in favor of UIPopoverPresentationController introduced in ios8. (I went through this transition also when going from UIActionSheet to UIAlertController.) You have two choices (example in obj-C):
A. Implement the UIViewController method below (UIKit calls this method before changing the size of a presented view controller’s view).
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:nil
completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// Fix up popover placement if necessary, *after* the transition.
// Be careful here if a subclass also overrides this method.
if (self.presentedViewController) {
UIPopoverPresentationController *presentationController =
[self.presentedViewController popoverPresentationController];
UIView *selectedView = /** YOUR VIEW */;
presentationController.sourceView = selectedView.superview;
presentationController.sourceRect = selectedView.frame;
}
}];
}
B. Alternatively, when configuring your UIPopoverPresentationController to present, also set its delegate. e.g. your presenting vc can implement UIPopoverPresentationControllerDelegate and assign itself as the delegate. Then implement the delegate method:
- (void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView * _Nonnull *)view {
UIView *selectedView = /** YOUR VIEW */;
// Update where the arrow pops out of in the view you selected.
*view = selectedView;
*rect = selectedView.bounds;
}

For Swift:
func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>)
{
rect.pointee = CGRect(x: self.view.frame.size.width, y: 0, width: 1, height: 1) // Set new rect here
}

I've tried just to set new rect (rect.initialize(...)) and it works.
func popoverPresentationController(popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverToRect rect: UnsafeMutablePointer<CGRect>, inView view: AutoreleasingUnsafeMutablePointer<UIView?>) {
if popoverPresentationController.presentedViewController.view.tag == Globals.PopoverTempTag
{
rect.initialize(getForPopupSourceRect())
}
}

I have similar problem which I resolve by this
[myPop presentPopoverFromRect:myfield.frame inView:myscrollview permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
Where myfield is frame from which you want to show your popover and myscrollview is container view in which you add your popover as subview(in my case its my scrollview, instead of putting inView:self.view I use inView:myscrollview).

Initialize PopOver Controller
var popoverContent: PopoverContentViewController?
Write Defination for PopOver Controller
popoverContent = self.storyboard?.instantiateViewController(withIdentifier: "PopoverContentViewController") as? PopoverContentViewController
popoverContent?.modalPresentationStyle = .popover
let popover = popoverContent?.popoverPresentationController!
popover?.delegate = self
popoverContent?.preQuestionInfoPopUpViewDelegateObject = self
popover?.permittedArrowDirections = UIPopoverArrowDirection()
popover?.sourceView = self.view
popover?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 330, height: 330)
Present PopOver Controller
self.present(popoverContent, animated: true, completion:nil)
Write below method and assign new size to popover:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let popover = popoverContent?.popoverPresentationController!
popover?.sourceRect = CGRect(x: size.width/2, y: size.height/2, width: 0, height: 0)
}

I had a same problem. Instead of performing -presentPopoverFromRect each time by keeping track of the source rectangle / view from which it is presented, I subclassed UIPopoverController. After doing it, all you have to do is set either the UIBarButtonItem / UIView from where the popover has to be displayed. You can even opt for displaying the popover from custom frame which can be passed in as a NSString value.
CSPopoverController.h:
#import <UIKit/UIKit.h>
// The original popover controller would not re-orientate itself when the orientation change occurs. To tackle that issue, this subclass is created
#interface CSPopoverController : UIPopoverController
#property (nonatomic, strong) NSString *popoverDisplaySourceFrame; // Mutually Exclusive. If you want to set custom rect as source, make sure that popOverDisplaySource is nil
#property (nonatomic, strong) id popoverDisplaySource; // Mutually exclusive. If UIBarButtonItem is set to it, popoverDisplaySourceFrame is neglected.
#property (nonatomic, strong) UIView *popoverDisplayView;
#property (nonatomic, assign, getter = shouldAutomaticallyReorientate) BOOL automaticallyReorientate;
-(void)reorientatePopover;
#end
CSPopoverController.m:
#import "CSPopoverController.h"
#implementation CSPopoverController
#synthesize popoverDisplaySourceFrame = popoverDisplaySourceFrame_;
-(NSString*)popoverDisplaySourceFrame
{
if (nil==popoverDisplaySourceFrame_)
{
if (nil!=self.popoverDisplaySource)
{
if ([self.popoverDisplaySource isKindOfClass:[UIView class]])
{
UIView *viewSource = (UIView*)self.popoverDisplaySource;
[self setPopoverDisplaySourceFrame:NSStringFromCGRect(viewSource.frame)];
}
}
}
return popoverDisplaySourceFrame_;
}
-(void)setPopoverDisplaySourceFrame:(NSString *)inPopoverDisplaySourceFrame
{
if (inPopoverDisplaySourceFrame!=popoverDisplaySourceFrame_)
{
popoverDisplaySourceFrame_ = inPopoverDisplaySourceFrame;
[self reorientatePopover];
}
}
#synthesize popoverDisplaySource = popoverDisplaySource_;
-(void)setPopoverDisplaySource:(id)inPopoverDisplaySource
{
if (inPopoverDisplaySource!=popoverDisplaySource_)
{
[self unlistenForFrameChangeInView:popoverDisplaySource_];
popoverDisplaySource_ = inPopoverDisplaySource;
[self reorientatePopover];
if ([popoverDisplaySource_ isKindOfClass:[UIView class]])
{
UIView *viewSource = (UIView*)popoverDisplaySource_;
[self setPopoverDisplaySourceFrame:NSStringFromCGRect(viewSource.frame)];
}
if (self.shouldAutomaticallyReorientate)
{
[self listenForFrameChangeInView:popoverDisplaySource_];
}
}
}
#synthesize popoverDisplayView = popoverDisplayView_;
-(void)setPopoverDisplayView:(UIView *)inPopoverDisplayView
{
if (inPopoverDisplayView!=popoverDisplayView_)
{
popoverDisplayView_ = inPopoverDisplayView;
[self reorientatePopover];
}
}
#synthesize automaticallyReorientate = automaticallyReorientate_;
-(void)setAutomaticallyReorientate:(BOOL)inAutomaticallyReorientate
{
if (inAutomaticallyReorientate!=automaticallyReorientate_)
{
automaticallyReorientate_ = inAutomaticallyReorientate;
if (automaticallyReorientate_)
{
[self listenForAutorotation];
[self listenForFrameChangeInView:self.popoverDisplaySource];
}
else
{
[self unlistenForAutorotation];
[self unlistenForFrameChangeInView:self.popoverDisplaySource];
}
}
}
-(void)listenForAutorotation
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
-(void)unlistenForAutorotation
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
-(void)listenForFrameChangeInView:(id)inView
{
// Let's listen for changes in the view's frame and adjust the popover even if the frame is updated
if ([inView isKindOfClass:[UIView class]])
{
UIView *viewToObserve = (UIView*)inView;
[viewToObserve addObserver:self
forKeyPath:#"frame"
options:NSKeyValueObservingOptionNew
context:nil];
}
}
-(void)unlistenForFrameChangeInView:(id)inView
{
if ([inView isKindOfClass:[UIView class]])
{
UIView *viewToObserve = (UIView*)inView;
[viewToObserve removeObserver:self
forKeyPath:#"frame"];
}
}
// TODO: Dealloc is not called, check why? !!!
- (void)dealloc
{
[self unlistenForFrameChangeInView:self.popoverDisplaySource];
[self unlistenForAutorotation];
DEBUGLog(#"dealloc called for CSPopoverController %#", self);
}
#pragma mark - Designated initializers
-(id)initWithContentViewController:(UIViewController *)viewController
{
self = [super initWithContentViewController:viewController];
if (self)
{
[self popoverCommonInitializations];
}
return self;
}
-(void)popoverCommonInitializations
{
[self setAutomaticallyReorientate:YES];
}
#pragma mark - Frame
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object==self.popoverDisplaySource)
{
[self setPopoverDisplaySourceFrame:nil];
[self reorientatePopover];
}
}
#pragma mark - Orientation
-(void)orientationChanged:(NSNotification *)inNotification
{
[self reorientatePopover];
}
-(void)reorientatePopover
{
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:#selector(performReorientatePopover)
object:nil];
// if ([self isPopoverVisible])
{
[self performSelector:#selector(performReorientatePopover)
withObject:nil
afterDelay:0.0];
}
}
-(void)performReorientatePopover
{
if (self.popoverDisplaySourceFrame && self.popoverDisplayView)
{
[self presentPopoverFromRect:CGRectFromString(self.popoverDisplaySourceFrame)
inView:self.popoverDisplayView
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
else if (self.popoverDisplaySource && [self.popoverDisplaySource isKindOfClass:[UIBarButtonItem class]])
{
UIBarButtonItem *barButton = (UIBarButtonItem*)self.popoverDisplaySource;
[self presentPopoverFromBarButtonItem:barButton
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}
#end
Usage:
If it is a UIBarButtonItem from where you are presenting it:
CSPopoverController *popOverCont = [[CSPopoverController alloc]initWithContentViewController:navCont];
self.popOver = popOverCont;
[popOverCont setPopoverDisplaySource:self.settingsButtonItem];
If it is a UIView from where you are presenting the popover:
CSPopoverController *popOver = [[CSPopoverController alloc] initWithContentViewController:navigation];
self.iPadPopoverController = popOver;
[newDateVC setIPadPopoverController:self.iPadPopoverController];
[popOver setPopoverDisplaySource:inButton];
[popOver setPopoverDisplayView:inView];

For iOS > 8 John Strickers answer helped but didn't do what I wanted it to do.
Here's the solution that worked for me. (If you want to download a full sample project it's here: https://github.com/appteur/uipopoverExample)
I created a property to hold any popover I wanted to present and also added a property to track the sourceRect and another for the view of the button I wanted the popover arrow to point at.
#property (nonatomic, weak) UIView *activePopoverBtn;
#property (nonatomic, strong) PopoverViewController *popoverVC;
#property (nonatomic, assign) CGRect sourceRect;
The button that triggered my popover is in a UIToolbar. When tapped it runs the following method that creates and launches the popover.
-(void) buttonAction:(id)sender event:(UIEvent*)event
{
NSLog(#"ButtonAction");
// when the button is tapped we want to display a popover, so setup all the variables needed and present it here
// get a reference to which button's view was tapped (this is to get
// the frame to update the arrow to later on rotation)
// since UIBarButtonItems don't have a 'frame' property I found this way is easy
UIView *buttonView = [[event.allTouches anyObject] view];
// set our tracker properties for when the orientation changes (handled in the viewWillTransitionToSize method above)
self.activePopoverBtn = buttonView;
self.sourceRect = buttonView.frame;
// get our size, make it adapt based on our view bounds
CGSize viewSize = self.view.bounds.size;
CGSize contentSize = CGSizeMake(viewSize.width, viewSize.height - 100.0);
// set our popover view controller property
self.popoverVC = [[UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]] instantiateViewControllerWithIdentifier:#"PopoverVC"];
// configure using a convenience method (if you have multiple popovers this makes it faster with less code)
[self setupPopover:self.popoverVC
withSourceView:buttonView.superview // this will be the toolbar
sourceRect:self.sourceRect
contentSize:contentSize];
[self presentViewController:self.popoverVC animated:YES completion:nil];
}
The 'setupPopover:withSourceView:sourceRect:contentSize method is simply a convenience method to set the popoverPresentationController properties if you plan to display multiple popovers and want them configured the same. It's implementation is below.
// convenience method in case you want to display multiple popovers
-(void) setupPopover:(UIViewController*)popover withSourceView:(UIView*)sourceView sourceRect:(CGRect)sourceRect contentSize:(CGSize)contentSize
{
NSLog(#"\npopoverPresentationController: %#\n", popover.popoverPresentationController);
popover.modalPresentationStyle = UIModalPresentationPopover;
popover.popoverPresentationController.delegate = self;
popover.popoverPresentationController.sourceView = sourceView;
popover.popoverPresentationController.sourceRect = sourceRect;
popover.preferredContentSize = contentSize;
popover.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionDown;
popover.popoverPresentationController.backgroundColor = [UIColor whiteColor];
}
For iOS 8 and up the viewWillTransitionToSize:withTransitionCoordinator get's called on the view controller when the device rotates.
I implemented this method in my presenting view controller class as shown below.
// called when rotating a device
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
NSLog(#"viewWillTransitionToSize [%#]", NSStringFromCGSize(size));
// resizes popover to new size and arrow location on orientation change
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context)
{
if (self.popoverVC)
{
// get the new frame of our button (this is our new source rect)
CGRect viewframe = self.activePopoverBtn ? self.activePopoverBtn.frame : CGRectZero;
// update our popover view controller's sourceRect so the arrow will be pointed in the right place
self.popoverVC.popoverPresentationController.sourceRect = viewframe;
// update the preferred content size if we want to adapt the size of the popover to fit the new bounds
self.popoverVC.preferredContentSize = CGSizeMake(self.view.bounds.size.width -20, self.view.bounds.size.height - 100);
}
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// anything you want to do when the transition completes
}];
}

Swift 3:
class MyClass: UIViewController, UIPopoverPresentationControllerDelegate {
...
var popover:UIPopoverPresentationController?
...
// Where you want to set the popover...
popover = YourViewController?.popoverPresentationController
popover?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
popover?.delegate = self
...
// override didRotate...
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
popover?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
}
}

I have popoverPresentationController that I present on a view that has a "fake" nav bar. So I can't attach the popoverPresentationController to a barButtonItem. My popup appears in the right place but does not when the screen rotates.
So for some reason popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>) does not get called for me.
To work around this (iOS 12, Swift 4.2) I added constraints to the popup in the completion closure when calling present. Now my popup stays where I would expect it too.
present(viewController, animated: true) { [weak self] in
DDLogDebug(String(describing: viewController.view.frame))
if let containerView = viewController.popoverPresentationController?.containerView,
let presentedView = viewController.popoverPresentationController?.presentedView,
let imageView = self?.headerView.settingsButton {
withExtendedLifetime(self) {
let deltaY:CGFloat = presentedView.frame.origin.y - imageView.frame.maxY
let topConstraint = NSLayoutConstraint.init(item: presentedView, attribute: .top, relatedBy: .equal, toItem: imageView.imageView, attribute: .bottom, multiplier: 1, constant: deltaY)
topConstraint?.priority = UILayoutPriority(rawValue: 999)
topConstraint?.isActive = true
let heightContraint = NSLayoutConstraint.init(item: presentedView, attribute: .height, relatedBy: .equal, toItem: containerView, attribute: .height, multiplier: 0.75, constant: -deltaY)
heightContraint?.isActive = true
let leftConstraint = NSLayoutConstraint.init(item: presentedView, attribute: .left, relatedBy: .equal, toItem: containerView, attribute: .left, multiplier: 1, constant: presentedView.frame.origin.x)
leftConstraint.isActive = true
let widthConstraint = NSLayoutConstraint.init(item: presentedView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: presentedView.frame.width)
widthConstraint.isActive = true
presentedView.translatesAutoresizingMaskIntoConstraints = false
}
}
}

Related

How to set size for popover? [duplicate]

I have a UIPopoverController hosting a UINavigationController, which contains a small hierarchy of view controllers.
I followed the docs and for each view controller, I set the view's popover-context size like so:
[self setContentSizeForViewInPopover:CGSizeMake(320, 500)];
(size different for each controller)
This works as expected as I navigate forward in the hierarchy-- the popover automatically animates size changes to correspond to the pushed controller.
However, when I navigate "Back" through the view stack via the navigation bar's Back button, the popover doesn't change size-- it remains as large as the deepest view reached. This seems broken to me; I'd expect the popover to respect the sizes that are set up as it pops through the view stack.
Am I missing something?
Thanks.
I was struggling with the same issue. None of the above solutions worked for me pretty nicely, that is why I decided to do a little investigation and find out how this works.
This is what I discovered:
When you set the contentSizeForViewInPopover in your view controller it won't be changed by the popover itself - even though popover size may change while navigating to different controller.
When the size of the popover will change while navigating to different controller, while going back, the size of the popover does not restore
Changing size of the popover in viewWillAppear gives very strange animation (when let's say you popController inside the popover) - I'd not recommend it
For me setting the hardcoded size inside the controller would not work at all - my controllers have to be sometimes big sometimes small - controller that will present them have the idea about the size though
A solution for all that pain is as follows:
You have to reset the size of currentSetSizeForPopover in viewDidAppear. But you have to be careful, when you will set the same size as was already set in field currentSetSizeForPopover then the popover will not change the size. For this to happen, you can firstly set the fake size (which will be different than one which was set before) followed by setting the proper size. This solution will work even if your controller is nested inside the navigation controller and popover will change its size accordingly when you will navigate back between the controllers.
You could easily create category on UIViewController with the following helper method that would do the trick with setting the size:
- (void) forcePopoverSize {
CGSize currentSetSizeForPopover = self.contentSizeForViewInPopover;
CGSize fakeMomentarySize = CGSizeMake(currentSetSizeForPopover.width - 1.0f, currentSetSizeForPopover.height - 1.0f);
self.contentSizeForViewInPopover = fakeMomentarySize;
self.contentSizeForViewInPopover = currentSetSizeForPopover;
}
Then just invoke it in -viewDidAppear of desired controller.
Here's how I solved it for iOS 7 and 8:
In iOS 8, iOS is silently wrapping the view you want in the popover into the presentedViewController of the presentingViewController view controller. There's a 2014 WWDC video explaining what's new with the popovercontroller where they touch on this.
Anyways, for view controllers presented on the navigation controller stack that all want their own sizing, these view controllers need (under iOS 8) to call this code to dynamically set the preferredContentSize:
self.presentingViewController.presentedViewController.preferredContentSize = CGSizeMake(320, heightOfTable);
Replace heightOfTable with your computed table or view height.
In order to avoid a lot of duplicate code and to create a common iOS 7 and iOS 8 solution, I created a category on UITableViewController to perform this work when viewDidAppear is called in my tableviews:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setPopOverViewContentSize];
}
Category.h:
#import <UIKit/UIKit.h>
#interface UITableViewController (PreferredContentSize)
- (void) setPopOverViewContentSize;
#end
Category.m:
#import "Category.h"
#implementation UITableViewController (PreferredContentSize)
- (void) setPopOverViewContentSize
{
[self.tableView layoutIfNeeded];
int heightOfTable = [self.tableView contentSize].height;
if (heightOfTable > 600)
heightOfTable = 600;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0)
self.preferredContentSize=CGSizeMake(320, heightOfTable);
else
self.presentingViewController.presentedViewController.preferredContentSize = CGSizeMake(320, heightOfTable);
}
}
#end
This is an improvement on krasnyk's answer.
Your solution is great, but it isn't smoothly animated.
A little improvement gives nice animation:
Remove last line in the - (void) forcePopoverSize method:
- (void) forcePopoverSize {
CGSize currentSetSizeForPopover = self.contentSizeForViewInPopover;
CGSize fakeMomentarySize = CGSizeMake(currentSetSizeForPopover.width - 1.0f, currentSetSizeForPopover.height - 1.0f);
self.contentSizeForViewInPopover = fakeMomentarySize;
}
Put [self forcePopoverSize] in - (void)viewWillAppear:(BOOL)animated method:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self forcePopoverSize];
}
And finally - set desired size in - (void)viewDidAppear:(BOOL)animated method:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CGSize currentSetSizeForPopover = self.contentSizeForViewInPopover;
self.contentSizeForViewInPopover = currentSetSizeForPopover;
}
You need to set the content size again in viewWillAppear. By calling the delagate method in which you set the size of popovercontroller. I had also the same issue. But when I added this the problem solved.
One more thing: if you are using beta versions lesser than 5. Then the popovers are more difficult to manage. They seem to be more friendly from beta version 5. It's good that final version is out. ;)
Hope this helps.
In the -(void)viewDidLoad of all the view controllers you are using in navigation controller, add:
[self setContentSizeForViewInPopover:CGSizeMake(320, 500)];
I reset the size in the viewWillDisappear:(BOOL)animated method of the view controller that is being navigated back from:
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
CGSize contentSize = [self contentSizeForViewInPopover];
contentSize.height = 0.0;
self.contentSizeForViewInPopover = contentSize;
}
Then when the view being navigated back to appears, I reset the size appropriately:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
CGSize contentSize;
contentSize.width = self.contentSizeForViewInPopover.width;
contentSize.height = [[self.fetchedResultsController fetchedObjects] count] * self.tableView.rowHeight;
self.contentSizeForViewInPopover = contentSize;
}
For iOS 8 the following works:
- (void) forcePopoverSize {
CGSize currentSetSizeForPopover = self.preferredContentSize;
CGSize fakeMomentarySize = CGSizeMake(currentSetSizeForPopover.width - 1.0f, currentSetSizeForPopover.height - 1.0f);
self.preferredContentSize = fakeMomentarySize;
self.navigationController.preferredContentSize = fakeMomentarySize;
self.preferredContentSize = currentSetSizeForPopover;
self.navigationController.preferredContentSize = currentSetSizeForPopover;
}
BTW I think, this should be compatible with previous iOS versions...
Well i worked out. Have a look.
Made a ViewController in StoryBoard. Associated with PopOverViewController class.
import UIKit
class PopOverViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = CGSizeMake(200, 200)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: "dismiss:")
}
func dismiss(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
See ViewController:
//
// ViewController.swift
// iOS8-PopOver
//
// Created by Alvin George on 13.08.15.
// Copyright (c) 2015 Fingent Technologies. All rights reserved.
//
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate
{
func showPopover(base: UIView)
{
if let viewController = self.storyboard?.instantiateViewControllerWithIdentifier("popover") as? PopOverViewController {
let navController = UINavigationController(rootViewController: viewController)
navController.modalPresentationStyle = .Popover
if let pctrl = navController.popoverPresentationController {
pctrl.delegate = self
pctrl.sourceView = base
pctrl.sourceRect = base.bounds
self.presentViewController(navController, animated: true, completion: nil)
}
}
}
override func viewDidLoad(){
super.viewDidLoad()
}
#IBAction func onShow(sender: UIButton)
{
self.showPopover(sender)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
}
Note: The func showPopover(base: UIView) method should be placed before ViewDidLoad. Hope it helps !
For me this solutions works.
This is a method from my view controller which extends UITableViewController and is the root controller for UINavigationController.
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.contentSizeForViewInPopover = self.tableView.bounds.size;
}
And don't forget to set content size for view controller you gonna push into navigation stack
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath{
dc = [[DetailsController alloc] initWithBookmark:[[bookmarksArray objectAtIndex:indexPath.row] retain] bookmarkIsNew:NO];
dc.detailsDelegate = self;
dc.contentSizeForViewInPopover = self.contentSizeForViewInPopover;
[self.navigationController pushViewController:dc animated:YES];
}
if you can imagine the assambler, I think this is slightly better:
- (void) forcePopoverSize {
CGSize currentSetSizeForPopover = self.contentSizeForViewInPopover;
self.contentSizeForViewInPopover = CGSizeMake(0, 0);
self.contentSizeForViewInPopover = currentSetSizeForPopover;
}
The accepted answer is not working fine with iOS 8. What I did was creating my own subclass of UINavigationController for use in that popover and override the method preferredContentSize in this way:
- (CGSize)preferredContentSize {
return [[self.viewControllers lastObject] preferredContentSize];
}
Moreover, instead of calling forcePopoverSize (method implemented by #krasnyk) in viewDidAppear I decided to set a viewController (which shows popover) as a delegate for previously mentioned navigation (in popover) and do (what force method does) in:
-(void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
delegate method for a passed viewController. One important thing, doing forcePopoverSize in a UINavigationControllerDelegate method is fine if you do not need that animation to be smooth if so then do leave it in viewDidAppear.
I was facing same problem, but you don't want to set contentsize in viewWillAppear or viewWillDisappear method.
AirPrintController *airPrintController = [[AirPrintController alloc] initWithNibName:#"AirPrintController" bundle:nil];
airPrintController.view.frame = [self.view frame];
airPrintController.contentSizeForViewInPopover = self.contentSizeForViewInPopover;
[self.navigationController pushViewController:airPrintController animated:YES];
[airPrintController release];
set contentSizeForViewInPopover property for that controller before pushing that controller to navigationController
I've had luck by putting the following in the viewdidappear:
[self.popoverController setPopoverContentSize:self.contentSizeForViewInPopover animated:NO];
Although this may not animate nicely in the case when you're pushing/popping different-sized popovers. But in my case, works perfectly!
All that you have to do is:
-In the viewWillAppear method of the popOvers contentView, add the snippet given below. You will have to specify the popOver's size first time when it is loaded.
CGSize size = CGSizeMake(width,height);
self.contentSizeForViewInPopover = size;
I had this issue with a popover controller whose popoverContentSize = CGSizeMake(320, 600) at the start, but would get larger when navigating through its ContentViewController (a UINavigationController).
The nav controller was only pushing and popping custom UITableViewControllers, so in my custom table view controller class's viewDidLoad i set self.contentSizeForViewInPopover = CGSizeMake(320, 556)
The 44 less pixels are to account for the Nav controller's nav bar, and now I don't have any issues anymore.
Put this in all view controllers you are pushing inside the popover
CGSize currentSetSizeForPopover = CGSizeMake(260, 390);
CGSize fakeMomentarySize = CGSizeMake(currentSetSizeForPopover.width - 1.0f,
currentSetSizeForPopover.height - 1.0f);
self.contentSizeForViewInPopover = fakeMomentarySize;
self.contentSizeForViewInPopover = currentSetSizeForPopover;
Faced the same issue and fixed it by setting content view size to navigation controller and view controller before the init of UIPopoverController was placed.
CGSize size = CGSizeMake(320.0, _options.count * 44.0);
[self setContentSizeForViewInPopover:size];
[self.view setFrame:CGRectMake(0.0, 0.0, size.width, size.height)];
[navi setContentSizeForViewInPopover:size];
_popoverController = [[UIPopoverController alloc] initWithContentViewController:navi];
I'd just like to offer up another solution, as none of these worked for me...
I'm actually using it with this https://github.com/nicolaschengdev/WYPopoverController
When you first call your popup use this.
if ([sortTVC respondsToSelector:#selector(setPreferredContentSize:)]) {
sortTVC.preferredContentSize = CGSizeMake(popoverContentSortWidth,
popoverContentSortHeight);
}
else
{
sortTVC.contentSizeForViewInPopover = CGSizeMake(popoverContentSortWidth,
popoverContentSortHeight);
}
Then in that popup use this.
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
if ([self respondsToSelector:#selector(setPreferredContentSize:)]) {
self.preferredContentSize = CGSizeMake(popoverContentMainWidth,
popoverContentMainheight);
}
else
{
self.contentSizeForViewInPopover = CGSizeMake(popoverContentMainWidth,
popoverContentMainheight);
}
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:YES];
self.contentSizeForViewInPopover = CGSizeZero;
}
Then repeat for child views...
This is the correct way in iOS7 to do this,
Set the preferred content size in viewDidLoad in each view controller in the navigation stack (only done once). Then in viewWillAppear get a reference to the popover controller and update the contentSize there.
-(void)viewDidLoad:(BOOL)animated
{
...
self.popoverSize = CGSizeMake(420, height);
[self setPreferredContentSize:self.popoverSize];
}
-(void)viewWillAppear:(BOOL)animated
{
...
UIPopoverController *popoverControllerReference = ***GET REFERENCE TO IT FROM SOMEWHERE***;
[popoverControllerReference setPopoverContentSize:self.popoverSize];
}
#krasnyk solution worked well in previous iOS versions but not working in iOS8. The following solution worked for me.
- (void) forcePopoverSize {
CGSize currentSetSizeForPopover = self.preferredContentSize;
//Yes, there are coupling. We need to access the popovercontroller. In my case, the popover controller is a weak property in the app's rootVC.
id mainVC = [MyAppDelegate appDelegate].myRootVC;
if ([mainVC valueForKey:#"_myPopoverController"]) {
UIPopoverController *popover = [mainVC valueForKey:#"_myPopoverController"];
[popover setPopoverContentSize:currentSetSizeForPopover animated:YES];
}
}
It is not the best solution, but it works.
The new UIPopoverPresentationController also has the resizing issue :( .
You need to set the preferredContentSizeproperty of the NavigationController in viewWillAppear:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationController.preferredContentSize = CGSizeMake(320, 500);}

Pop up UIViewController with an IBAction UIViewController

Is it possible to pop up an UIViewController (xib file) like UIPopOverControl in iPad ?
I have a separate NIB file which is linked to an UIViewController. I want to popup that NIB file along with the button pressed with a customised size (200,200).
Is this possible?
I am trying to get something like this on the iPhone - http://www.freeimagehosting.net/c219p
You can also use one of these custom made clases to show a popup:
https://github.com/sonsongithub/PopupView
https://github.com/werner77/WEPopover
https://github.com/50pixels/FPPopover
Example with FPPopover:
//the view controller you want to present as popover
YourViewController *controller = [[YourViewController alloc] init];
//our popover
FPPopoverController *popover = [[FPPopoverController alloc] initWithViewController:controller];
//the popover will be presented from the okButton view
[popover presentPopoverFromView:okButton];
//release if you arent using ARC
[controller release];
yes it is. load Your pOpOver controller lazily at the point when it is needed. add its view as a subview (you could animate the addition). make its frame size what You need and add the image You have shown as a background subview of the pOpOver controller along with other controls You want in the pop up.
good luck
UPDATE:
alright, ii will show You how ii do this in my app Lucid Reality Check (deployment target iOS4.3).
one can use a UIPopoverController to present another controllers view. what ii do first is to make sure ii always know the current orientation of the device, so ii can reposition the popup on rotation (maybe this works by itself on iOS6?). so in my base controller (from where ii want to show a popup) ii have an instance variable like this:
UIInterfaceOrientation toOrientation;
and also:
UIPopoverController *popover;
UIButton *popover_from_button;
BOOL representPopover;
popover will be reused for all popups, and popover_from_button will hold the button from which the popup is initiated.
then the next code comes into the base controller:
- (void)popoverWillRotate {
if ([popover isPopoverVisible]) {
[self dismissPopover];
representPopover = YES;
}
}
- (void)popoverDidRotate {
if (popover && representPopover) {
representPopover = NO;
[self representPopover];
}
}
these two methods have to be called every time the device is rotated, like this:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//DLOG(#"willRotateTo %i", toInterfaceOrientation);
toOrientation = toInterfaceOrientation;
if ([Kriya isPad ]) {
[self popoverWillRotate];
}
}
as one can see, first the orientation is captured then popoverWillRotate is called. this would hide the popover during the orientation animation. and after rotating, the popover must be redisplayed like this:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
//DLOG(#"didRotateFrom %i", fromInterfaceOrientation);
//[self layout:toOrientation]; //do some layout if You need
if ([Kriya isPad]) {
[self popoverDidRotate];
}
}
- (void)layout:(UIInterfaceOrientation)toInterfaceOrientation {
//one can do view layout here, and call other controllers to do their layout too
}
now that the orientation changes are worked out, the code for presenting the popover arrives here:
#pragma mark Popovers
- (void)presentPopoverWith:(id)controller fromButton:(UIButton*)button {
if (popover)
[popover release];
if (popover_from_button)
[popover_from_button release];
popover_from_button = [button retain];
popover = [[UIPopoverController alloc] initWithContentViewController:controller];
[popover setDelegate:self];
[self representPopover];
}
- (void)representPopover{
if (popover) {
UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirectionAny;
UIViewController *vc = (UIViewController*)[popover contentViewController];
CGSize contentSize = [vc contentSizeForViewInPopover];
if (contentSize.width > 0 && contentSize.height > 0) {
[popover setPopoverContentSize:contentSize animated:NO];
}
//DLOG(#"representPopover rect:%#", [Kriya printRect:popover_from_button.frame]);
[popover presentPopoverFromRect:CGRectOffset(popover_from_button.frame, 0, popover_from_button.frame.size.height + 7.0) inView:self.view permittedArrowDirections:arrowDirection animated:YES];
}
}
- (void)dismissPopover {
if (popover) {
[popover dismissPopoverAnimated:YES];
}
}
finally, if one wants to be notified when the popover is dismissed, the base controller must implement a delegate method:
#pragma mark UIPopoverControllerDelegate
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
//do something important here like drink some water
}
and don't forget to make the base controller a UIPopoverControllerDelegate in its header.
a use case for this way of doing popups would then look like this:
- (void)takeImage {
UIImagePickerController *picker = [[[UIImagePickerController alloc] init] autorelease];
[picker setDelegate:self];
[picker setAllowsEditing:NO];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[picker setSourceType:UIImagePickerControllerSourceTypeCamera];
if ([Kriya isPad]) {
[self presentPopoverWith:picker fromButton:backgroundImageButton];
} else {
//modals on iPhone/iPod
//DLOG(#"takeImage addSubview picker");
[self presentModalViewController:picker animated:YES];
}
} else {
//DLOG(#"no camera");
}
}
this would use an image picker as the content for the popup, but one can use any controller with a valid view. so just do this:
[self presentPopoverWith:popupsContentController fromButton:tappedButton];
one should not have any missing information, :), the method [Kriya isPad] is just this:
+ (BOOL)isPad {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 30200
// iPad capable OS
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
//this is an iPad
return YES;
}else {
//this is an iPod/iPhone
return NO;
}
#else
//can not pissible be iPad
return NO;
#endif
}
ENJOY!

CATransition also animating the navigationBar

I want to make a custom animation to pop my navigation controller. I only want to animate the view, not the navigationBar. With this code I animate both, the view and the navigationBar. How can I only animate the view??
CATransition* transition = [CATransition animation];
transition.duration = 0.3;
transition.type = kCATransitionFade;
transition.subtype = kCATransitionFromTop;
[self.navigationController.view.layer addAnimation:transition forKey:kCATransition];
[self.navigationController popViewControllerAnimated:NO];
this code is fired when a custom back button added inside the navigationcontroller bar is pressed.
Here is a code that does custom animation both for back button and when you call popRootViewController: method.
It's a class that extends UINavigationViewController which by itself contradicts Apple's docs also it assigns private variable using KVO, which might stop working as soon as engineers change UINavigationController class so use it a t your own risk.
#import "MyNavigationController.h"
#interface MyNavigationController () <UINavigationBarDelegate> {
// Flag that we will use to avoid collisions between navgiation bar
// when we call popViewControllerAnimated: method directly
BOOL _isPopping;
}
- (UIViewController *)myPopViewControllerAniamted:(BOOL)animated;
#end
#implementation MyNavigationController
- (id)init
{
self = [super init];
if (!self) return nil;
// We can't intercept delegation of the original navigation bar,
// we have to replace it with our own, by assigning new instance to
// the private _navigationBar vairable
UINavigationBar *navigationBar = [[UINavigationBar alloc] init];
navigationBar.delegate = self;
[self setValue:navigationBar forKey:#"_navigationBar"];
return self;
}
// This is the delegate method called when you're about to pop navigation item
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
// If we're in the process of popping items we don't want to reenter
if (!_isPopping) {
[self myPopViewControllerAniamted:YES];
}
return YES;
}
// Similarly we have to override popToRootViewControllerAnimated:
// The only difference would be that we use not previous view as a
// target for the transfition, but the very first view
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
return [self myPopViewControllerAniamted:animated];
}
// Our custom popping method
- (UIViewController *)myPopViewControllerAniamted:(BOOL)animated
{
_isPopping = YES;
// If we got here, we have at least two view controllers in the stack
UIViewController *currentViewController = self.topViewController;
if (animated && self.viewControllers.count > 1) {
UIView *currentView = currentViewController.view;
UIViewController *previousViewController = [self.viewControllers objectAtIndex:self.viewControllers.count - 2];
UIView *previousView = previousViewController.view;
previousView.alpha = 0.0;
[currentView.superview insertSubview:previousView belowSubview:currentView];
// I use UIView just for the sake of the simplicity of this example
// In case of core animation you will have to deal with delegates
// to trigger view controller popping when animation finishes
[UIView animateWithDuration:0.33 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
currentView.alpha = 0.0;
previousView.alpha = 1.0;
} completion:^(BOOL finished) {
[super popViewControllerAnimated:NO];
_isPopping = NO;
}];
} else {
[super popViewControllerAnimated:NO];
_isPopping = NO;
}
return currentViewController;
}
#end
Once again, it was done purely as exercise of what is possible, I would highly recommend reading UIViewController guide, probably Container View Controller can satisfy you needs as a designated way of customizing view controller behaviour.
Hope it helps!

How do I do a Fade/No transition between view controllers

Is it possible to do a fade in and fade out transition between View Controllers in Storyboard. Or without transition.
If it's possible, what's the code for it?
If presenting a modal view controller, you can specify a modalTransitionStyle of UIModalTransitionStyleCrossDissolve. If doing this with a segue in your storyboard, select the attributes inspector for the segue, and specify the transition style there:
If presenting the view controller programmatically, you can define your modal segue between the view controllers in your storyboard with a "Transition" of "Cross Dissolve" and then have the source view controller perform this segue:
[self performSegueWithIdentifier:#"presentSegue" sender:sender];
Or, if you are calling presentViewController:
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"YourStoryboardID"];
controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:controller animated:YES completion:nil];
In iOS 7, Apple has provided a new technology that provides a rich and robust control for highly customized transitions. For more information, refer to WWDC 2013 video Custom Transitions Using View Controllers.
But, for instance, if you want to customize the push and pop animations in iOS 7 to fade, you would specify a delegate for the navigation controller
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.delegate = self;
}
You would then implement animationControllerForOperation that specified the animator objects for pushing and popping:
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController*)fromVC
toViewController:(UIViewController*)toVC
{
if (operation == UINavigationControllerOperationPush)
return [[PushAnimator alloc] init];
if (operation == UINavigationControllerOperationPop)
return [[PopAnimator alloc] init];
return nil;
}
You'd obviously have to implement your custom push and pop animators, such as:
#interface PushAnimator : NSObject <UIViewControllerAnimatedTransitioning>
#end
#implementation PushAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 0.0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
#end
And
#interface PopAnimator : NSObject <UIViewControllerAnimatedTransitioning>
#end
#implementation PopAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.alpha = 0.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
#end
There is a similar, but slightly different technique for customizing modal transitions, too (though if you were just doing a face, you'd probably just use the modalTransitionStyle discussed above unless there was some other subtle customization you wanted to employ). See the aforementioned Custom Transitions Using View Controllers for more information.
Bottom line, custom transitions for iOS 7 are a slightly complicated, but very robust way to provide tremendous control over the animations for transitions.
For creating Custom Segue create subclass of UIStoryboard segue.
For example:
// MCFadeSegue.h
#import <UIKit/UIKit.h>
#interface MCFadeSegue : UIStoryboardSegue
#end
// MCFadeSegue.m
#import <QuartzCore/QuartzCore.h>
#import "MCFadeSegue.h"
#implementation MCFadeSegue
- (void)perform
{
CATransition *transition = [CATransition animation];
transition.duration = 0.5;
transition.type = kCATransitionFade;
[[[[[self sourceViewController] view] window] layer] addAnimation:transition
forKey:kCATransitionFade];
[[self sourceViewController]
presentViewController:[self destinationViewController]
animated:NO completion:NULL];
}
#end
Then in MainStoryboard.storyboard choose segue and set Style:Custom and Class:MCFadeSegue.
Push/Pop UIVIewController FadeIn/FadeOut in Swift
class FadeInPushSegue: UIStoryboardSegue {
var animated: Bool = true
override func perform() {
if var sourceViewController = self.sourceViewController as? UIViewController, var destinationViewController = self.destinationViewController as? UIViewController {
var transition: CATransition = CATransition()
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
sourceViewController.view.window?.layer.addAnimation(transition, forKey: "kCATransition")
sourceViewController.navigationController?.pushViewController(destinationViewController, animated: false)
}
}
}
class FadeOutPopSegue: UIStoryboardSegue {
override func perform() {
if var sourceViewController = self.sourceViewController as? UIViewController, var destinationViewController = self.destinationViewController as? UIViewController {
var transition: CATransition = CATransition()
transition.duration = 0.4
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
sourceViewController.view.window?.layer.addAnimation(transition, forKey: "kCATransition")
sourceViewController.navigationController?.popViewControllerAnimated(false)
}
}
}
Try this one.
let transition: CATransition = CATransition()
transition.duration = 0.4
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade
self.navigationController!.view.layer.addAnimation(transition, forKey: nil)
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("vcID") as! My_ViewController
self.navigationController?.pushViewController(vc, animated: false)
Without needing to create a custom segue, I put together this code to present the view...
UIViewController *nextView = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"YOUR_VIEW_CONTROLLER_STORYBOARD_NAME"];
nextView.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.navigationController presentViewController:nextView animated:YES completion:nil];
// (For a cross dissolve, set animated:YES. For no transition, set animated:NO.)
Hope this helps anyone who comes across this question!
My Swift Version with optional checking offcourse!
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
if let stView = storyboard.instantiateViewControllerWithIdentifier("STVC") as? STVC {
stView.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
self.navigationController?.presentViewController(stView, animated: true, completion: nil)
}
Just make sure to set the Storyboard ID of the "View controller" in IB
Push/Pop UIVIewController FadeIn/FadeOut in Swift 3 syntax, based on Eugene Braginets's answer
class FadeInPushSegue: UIStoryboardSegue {
var animated: Bool = true
override func perform() {
let sourceViewController = self.source
let destinationViewController = self.destination
let transition: CATransition = CATransition()
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
sourceViewController.view.window?.layer.add(transition, forKey: "kCATransition")
sourceViewController.navigationController?.pushViewController(destinationViewController, animated: false)
}
}
class FadeOutPopSegue: UIStoryboardSegue {
override func perform() {
let sourceViewController = self.source
let transition: CATransition = CATransition()
transition.duration = 0.4
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionFade; //kCATransitionMoveIn; //, kCATransitionPush, kCATransitionReveal, kCATransitionFade
sourceViewController.view.window?.layer.add(transition, forKey: "kCATransition")
_ = sourceViewController.navigationController?.popViewController(animated: false)
}
}
Swift 5:
After spending hours on finding the right solution Rob's one answered all of my needs, after a few adjustments it was perfect.
Create the fade View Controller class:
class FadeViewControllerTransition: NSObject, UIViewControllerAnimatedTransitioning {
var fadeOut: Bool = false
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if fadeOut {
animateFadeOut(using: transitionContext)
return
}
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
transitionContext.containerView.addSubview(toViewController.view)
toViewController.view.alpha = 0.0
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
toViewController.view.alpha = 1.0
}, completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
func animateFadeOut(using transitionContext: UIViewControllerContextTransitioning) {
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
transitionContext.containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
fromViewController.view.alpha = 0.0
}, completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
In your Navigation Controller's first View Controller:
class MainViewController: UIViewController, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate {
private let fadeVCTransition = FadeViewControllerTransition()
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
fadeVCTransition.fadeOut = (operation == .pop)
return fadeVCTransition
}
And that's it.
What I did was to use Storyboard in Interface Builder, connected the two viewcontroller you need to move to /from and back (hold ctrl and drag click from the origin viewcontroller to destination viewcontroller), and changed the segue properties. I used the following:
I then used the following:
[self performSegueWithIdentifier:#"showMovieDetailSegue" sender:self];
when you want to remove it, just call this from the origin viewcontroller:
[self dismissViewControllerAnimated:YES completion:nil];
Simple and very quick.
This drove me absolutely nuts for several hours. None of these solutions seemed to be what I wanted, and I was losing my mind. Finally came up with the following, which swaps view controllers within a single NSWindow using a fade out/fade in transition. First it performs a fade out, then, in the completion handler, it sets the view for the window to the new view and performs a fade in. Maybe there's a better way to do this, but this is the simplest way I could hack together.
class CustomSegue : NSStoryboardSegue {
override func perform() {
guard let fromController = sourceController as? NSViewController else {
return;
}
guard let toController = destinationController as? NSViewController else {
return;
}
let animationDuration = 0.5;
NSAnimationContext.runAnimationGroup({
$0.duration = animationDuration;
fromController.view.animator().alphaValue = 0.0;
}, completionHandler: {
fromController.view.window?.contentViewController = toController;
toController.view.alphaValue = 0.0;
NSAnimationContext.runAnimationGroup({
$0.duration = animationDuration;
toController.view.animator().alphaValue = 1.0;
})
});
}
}
The lack of examples in Apple documentation makes me want to tear my brain out, and the mix of Objective C and Swift everywhere is just... bah!
To present WITHOUT Transition:
Select your segue in the Storyboard
Uncheck "Animates" in the Attributes Inspector

draw rect from another class

i have two classes on different xibs, one has the window and a slider (controller 1), the other has a window and a view (controller 2). controller 1 is files owner of the first xib and controller 2 is a class of a NSView. What I want to do is when I move the slider I want to change the color of the NSRect.
In controller 1 I have my slider action:
-(IBAction)moveSlider:(id)sender
{
Controller2 *view = [[Controller2 alloc] init];
[view redraw];
}
and in controller 2 I have my draw rect and my method to redraw.
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
RectColor = [NSColor blackColor];
}
return self;
}
-(void) redraw
{
NSLog(#"changed");
RectColor = [NSColor blueColor];
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)rect
{
[RectColor set];
NSRectFill(rect);
}
the weird thing is when i have my action and the slider on the same xib as my view with the draw rect it works just fine. however when I have the slider in the other xib it wont work. any ideas? thanks!
In Controller1 the moveSlider: method creates a new instance of Controller2 every time it is called. Controller1 needs to keep a reference to the existing Controller2 and call the redraw method on that instance.