I'm trying to make a view fall as a custom segue transition, but when the perform method is called in the UIStoryboardSegue implementation, it does not fall. I have tried moving the view to be dropped into the source's view to see if it does anything, but it doesn't.
-(void)perform {
UIViewController *src = (UIViewController *)self.sourceViewController;
UIViewController *dest = (UIViewController *)self.destinationViewController;
UIView *viewdrop = [dest.view snapshotViewAfterScreenUpdates:YES];
viewdrop.frame = CGRectMake(0, -src.view.frame.size.height, dest.view.frame.size.width, dest.view.frame.size.height);
[src.view addSubview:viewdrop];
animator = [[UIDynamicAnimator alloc] initWithReferenceView:src.view];
UIGravityBehavior* gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[viewdrop]];
[animator addBehavior:gravityBehavior];
}
The reason it doesn't drop is because the gravity behavior takes time, but the segue itself is deallocated as soon as the perform method finishes. So, you need a way to keep the segue alive at least until the movement is complete. One way to do this is to make a strong property for the segue in the source view controller, and set its value in prepareForSegue,
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
self.dropSegue = segue;
}
I made a modified version of your segue that also adds a collision behavior, and sets the source view controller as the delegate of the collision behavior, so I can use the delegate method, collisionBehavior:endedContactForItem:withBoundaryIdentifier:, to set the dropSegue property to nil (after a slight delay) which causes the segue to be deallocated,
-(void)collisionBehavior:(UICollisionBehavior *)behavior endedContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier {
NSLog(#"collision ended with %#", identifier);
[self performSelector:#selector(setDropSegue:) withObject:nil afterDelay:1];
}
Here is my version of the gravity drop segue,
#interface RDSegue ()
#property (strong, nonatomic) UIDynamicAnimator *animator;
#end
#implementation RDSegue
-(id)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination {
if (self = [super initWithIdentifier:identifier source:source destination:destination]) {
UIViewController *src = self.sourceViewController;
UIViewController *dest = self.destinationViewController;
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:src.view];
[src addChildViewController:dest];
[dest didMoveToParentViewController:src];
dest.view.frame = CGRectMake(0, -src.view.bounds.size.height, src.view.bounds.size.width, src.view.bounds.size.height);
[src.view addSubview:dest.view];
}
return self;
}
-(void)perform {
UIGravityBehavior* gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[[self.destinationViewController view]]];
UICollisionBehavior *collide = [[UICollisionBehavior alloc] initWithItems:#[[self.destinationViewController view]]];
CGPoint left = CGPointMake(self.animator.referenceView.bounds.origin.x, self.animator.referenceView.bounds.origin.y + self.animator.referenceView.bounds.size.height);
CGPoint right = CGPointMake(self.animator.referenceView.bounds.origin.x + self.animator.referenceView.bounds.size.width, self.animator.referenceView.bounds.origin.y + self.animator.referenceView.bounds.size.height);
[collide addBoundaryWithIdentifier:#"bottom" fromPoint:left toPoint:right];
[collide setCollisionDelegate:self.sourceViewController];
[self.animator addBehavior:gravityBehavior];
[self.animator addBehavior:collide];
}
-(void)dealloc {
NSLog(#"In dealloc");
}
Related
I am trying to achieve a modal presentation effect where the presented view covers the parent view only partially as shown in the picture below.
I know I could achieve this by implementing custom transitions using UIPresentationController. I don't want to reinvent the wheel so before I roll on with development I would like to ask.
Is there a build in support for this kind of transition in the APIs?
I researched all available Modal Presentation Styles and it appears to me there is no support for the transition I want to make and the only way of achieving it is just to code it.
I ran into this exact same issue. I went down the modal presentation styles route as well and kept hitting a wall (specifically getting it working on an iPhone rather than an iPad).
After some digging around, I was able to get it working though. Here's how I did it:
To start, we need a view controller that we will be presenting (the modal one) to set it's view's background color to transparent and set the frame of the navigation controller's view to some offset.
ModalViewController.h
#import UIKit;
#class ModalViewController;
#protocol ModalViewControllerDelegate <NSObject>
- (void)modalViewControllerDidCancel:(ModalViewController *)modalViewController;
#end
#interface ModalViewController : UIViewController
#property (weak, nonatomic) id<ModalViewControllerDelegate> delegate;
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController;
#end
ModalViewController.m
static const CGFloat kTopOffset = 50.0f;
#implementation ModalViewController {
UINavigationController *_navController;
}
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController
{
self = [super initWithNibName:nil bundle:nil];
if (self) {
rootViewController.navigationItem.leftBarButtonItem = [self cancelButton];
_navController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.view.backgroundColor = [UIColor clearColor];
[self.view addSubview:_navController.view];
// this is important (prevents black overlay)
self.modalPresentationStyle = UIModalPresentationOverFullScreen;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect bounds = self.view.bounds;
_navController.view.frame = CGRectMake(0, kTopOffset, CGRectGetWidth(bounds), CGRectGetHeight(bounds) - kTopOffset);
}
- (UIBarButtonItem *)cancelButton
{
return [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style:UIBarButtonItemStylePlain target:self action:#selector(cancelButtonClicked:)];
}
- (void)cancelButtonClicked:(id)sender
{
[_delegate modalViewControllerDidCancel:self];
}
#end
Next, we need to set up the presenting controller to run the following animation:
Scale itself down
Fade out a lil' bit
Present the modal view controller using presentViewController:animated:completion
This is what I did
PresentingViewController.m
static const CGFloat kTransitionScale = 0.9f;
static const CGFloat kTransitionAlpha = 0.6f;
static const NSTimeInterval kTransitionDuration = 0.5;
#interface PresentingViewController <ModalViewControllerDelegate>
#end
#implementation PresentingViewController
...
...
- (void)showModalViewController
{
self.navigationController.view.layer.shouldRasterize = YES;
self.navigationController.view.layer.rasterizationScale = [UIScreen mainScreen].scale;
UIViewController *controller = // init some view controller
ModalViewController *container = [[ModalViewController alloc] initWithRootViewController:controller];
container.delegate = self;
__weak UIViewController *weakSelf = self;
[UIView animateWithDuration:kTransitionDuration animations:^{
weakSelf.navigationController.view.transform = CGAffineTransformMakeScale(kTransitionScale, kTransitionScale);
weakSelf.navigationController.view.alpha = kTransitionAlpha;
[weakSelf presentViewController:container animated:YES completion:nil];
} completion:^(BOOL finished) {
weakSelf.navigationController.view.layer.shouldRasterize = NO;
}];
}
#pragma mark - ModalViewControllerDelegate
- (void)modalViewControllerDidCancel:(ModalViewController *)modalViewController
{
__weak UIViewController *weakSelf = self;
[UIView animateWithDuration:kTransitionDuration animations:^{
weakSelf.navigationController.view.alpha = 1;
weakSelf.navigationController.view.transform = CGAffineTransformIdentity;
[weakSelf dismissViewControllerAnimated:YES completion:nil];
}];
}
#end
pretty sure its done like this
let newVC = <view controller you want to display>
let nav: UINavigationController = UINavigationController(rootViewController: newVC)
if let currVc = UIApplication.sharedApplication().keyWindow?.rootViewController {
nav.transitioningDelegate = currVc
nav.modalPresentationStyle = UIModalPresentationStyle.Custom;
currVc.presentViewController(nav, animated: true, completion: nil)
}
I'm pretty sure this is your answer - Page sheet - as in UIModalPresentationPageSheet
https://developer.apple.com/library/ios/documentation/userexperience/conceptual/mobilehig/Alerts.html#//apple_ref/doc/uid/TP40006556-CH14-SW3
The initial ViewController of my app has 2 container views: The first contains a ViewController inside which is a UINavigationController which controls the main content of the app. The second container view of the initial VC is a small strip with UILabels positioned directly below where the the Navigation Bar will be.
The initial VC of the navigation stack of the main content VC has the Navigation Bar hidden. All subsequent VC's will display the Navigation Bar along with the second container view of the initial VC. I would like to animate in the second container VC when the app navigates away from the initial VC of the navigation stack.
My question is, what animation do I need to create to match the animation a Navigation Controller performs when it pushes/pops VC on/off the navigation stack? I'm currently using:
[UIView animateWithDuration:0.27f delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.playerCardVC.frame = secondFrame;
} completion:nil];
But it's not matching up exactly when the Navigation Bar animates into view. Any thoughts/ideas would be greatly appreciated.
UPDATE:
I've been searching online for an answer to this issue along with trying to tweak the animation posted in my initial question but cannot seem to perfectly match the navigation bar as it animates into view. Any comments or points in the right direction will be truly appreciated.
It's very tough to understand your view hierarchy and navigation design from the description above, perhaps you can post some screenshots or sketches?
You can override the standard horizontal push/pop animations of a UINavigationController. You do so by defining a custom UINavigationControllerDelegate object and a few other things. See below.
Setup your navController and navControllerDelegate like so:
UINavigationController *navigationController = [[UINavigationController alloc] init];
self.navigationControllerDelegate = [[NavigationControllerDelegate alloc] init];
navigationController.delegate = self.navigationControllerDelegate;
Where the NavigationControllerDelegate class looks like this:
#interface NavigationControllerDelegate : NSObject <UINavigationControllerDelegate>
#end
#import "NavigationControllerDelegate.h"
#import "CrossFadePushAnimator.h"
#import "CrossFadePopAnimator.h"
#interface NavigationControllerDelegate ()
#property (nonatomic, strong) CrossFadePushAnimator* pushAnimator;
#property (nonatomic, strong) CrossFadePopAnimator* popAnimator;
#end
#implementation NavigationControllerDelegate
- (id)init
{
self = [super init];
if (self)
{
self.pushAnimator = [CrossFadePushAnimator new];
self.popAnimator = [CrossFadePopAnimator new];
}
return self;
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPop)
{
return self.popAnimator;
}
else if (operation == UINavigationControllerOperationPush)
{
return self.pushAnimator;
}
return nil;
}
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
return nil;
}
The pushAnimator looks something like this:
#interface CrossFadePushAnimator ()
#property (nonatomic, strong) id<UIViewControllerContextTransitioning> transitionContext;
#end
#implementation CrossFadePushAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return AnimationDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 1.0f;
// We are using CAAnimation instead of UIView animation because we need the UIToolbar blur layer to animate
CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:#"opacity"];
fade.fromValue = #0;
fade.toValue = #1;
fade.duration = [self transitionDuration:transitionContext];
fade.removedOnCompletion = YES;
fade.delegate = self;
self.transitionContext = transitionContext;
[toViewController.view.layer addAnimation:fade forKey:AnimationKey];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self.transitionContext completeTransition:![self.transitionContext transitionWasCancelled]];
self.transitionContext = nil;
}
And the popAnimator looks something like the pushAnimator.
Hope this helps!
It's very tough to understand your view hierarchy and navigation design from the description above, perhaps you can post some screenshots or sketches?
You can override the standard horizontal push/pop animations of a UINavigationController. You do so by defining a custom UINavigationControllerDelegate object and a few other things. See below.
Setup your navController and navControllerDelegate like so:
UINavigationController *navigationController = [[UINavigationController alloc] init];
self.navigationControllerDelegate = [[NavigationControllerDelegate alloc] init];
navigationController.delegate = self.navigationControllerDelegate;
Where the NavigationControllerDelegate class looks like this:
#interface NavigationControllerDelegate : NSObject <UINavigationControllerDelegate>
#end
#import "NavigationControllerDelegate.h"
#import "CrossFadePushAnimator.h"
#import "CrossFadePopAnimator.h"
#interface NavigationControllerDelegate ()
#property (nonatomic, strong) CrossFadePushAnimator* pushAnimator;
#property (nonatomic, strong) CrossFadePopAnimator* popAnimator;
#end
#implementation NavigationControllerDelegate
- (id)init
{
self = [super init];
if (self)
{
self.pushAnimator = [CrossFadePushAnimator new];
self.popAnimator = [CrossFadePopAnimator new];
}
return self;
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if (operation == UINavigationControllerOperationPop)
{
return self.popAnimator;
}
else if (operation == UINavigationControllerOperationPush)
{
return self.pushAnimator;
}
return nil;
}
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
return nil;
}
The pushAnimator looks something like this:
#interface CrossFadePushAnimator ()
#property (nonatomic, strong) id<UIViewControllerContextTransitioning> transitionContext;
#end
#implementation CrossFadePushAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return AnimationDuration;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 1.0f;
// I'm using CABasicAnimation here for a specific reason, but you could also use the animation method you use above.
CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:#"opacity"];
fade.fromValue = #0;
fade.toValue = #1;
fade.duration = [self transitionDuration:transitionContext];
fade.removedOnCompletion = YES;
fade.delegate = self;
self.transitionContext = transitionContext;
[toViewController.view.layer addAnimation:fade forKey:AnimationKey];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self.transitionContext completeTransition:![self.transitionContext transitionWasCancelled]];
self.transitionContext = nil;
}
And the popAnimator looks something like the pushAnimator.
Hope this helps!
I would like to present a modal View Controller with a custom Segue including UIKit Dynamics.
Copied the following code by rdelmar in this post https://stackoverflow.com/a/23039173/1016102 and tried to modify it for what i want.
-(id)initWithIdentifier:(NSString *)identifier source:(UIViewController *)source destination:(UIViewController *)destination {
if (self = [super initWithIdentifier:identifier source:source destination:destination]) {
UIViewController *src = self.sourceViewController;
UIViewController *dest = self.destinationViewController;
self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:src.view];
[src addChildViewController:dest];
[dest didMoveToParentViewController:src];
dest.view.frame = CGRectMake(0, 300, src.view.bounds.size.width, src.view.bounds.size.height);
[src.view addSubview:dest.view];
}
return self;
}
-(void)perform {
UIGravityBehavior* gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[[self.destinationViewController view]]];
CGVector vector = CGVectorMake(0, -1);
[gravityBehavior setGravityDirection:vector];
UICollisionBehavior *collide = [[UICollisionBehavior alloc] initWithItems:#[[self.destinationViewController view]]];
CGPoint left = CGPointMake(self.animator.referenceView.bounds.origin.x, self.animator.referenceView.bounds.origin.y + self.animator.referenceView.bounds.size.height);
CGPoint right = CGPointMake(self.animator.referenceView.bounds.origin.x + self.animator.referenceView.bounds.size.width, self.animator.referenceView.bounds.origin.y + self.animator.referenceView.bounds.size.height);
[collide addBoundaryWithIdentifier:#"top" fromPoint:left toPoint:right];
[collide setCollisionDelegate:self.sourceViewController];
[self.animator addBehavior:gravityBehavior];
[self.animator addBehavior:collide];
}
Sadly my destination Controller appears on the wrong position and is disapperaing directly after the collision.
I would like to achive some result as in the following sketch.
Actually my UI is acting like this: I tap the "+" button which fires my custom-segue and the the black box pops directly under the orange NavigationBar. it doesn't pop from the bottom border of the orange box like in the sketch. it just appears and disappers with popping over the top.
What should i edit to achive this?
I want to have a persistent button in the bottom right corner of my app. During all view transitions, the button should remain static. I'm having trouble deciding what view to add the button to. I know the button ought to be stored in the AppDelegate, but I don't know what other view it would be sense to add it to except the window. One downside of adding it to the window is that when there's an app running in the background (ie Phone), the added status bar padding will push down the window. In general, adding it to the window seems to be a hacky solution -- any thoughts?
Yes, adding it to the UIWindow would be extremely hacky and finicky.
Storyboards
If you're using Storyboards and iOS 5.0 onwards, you should be able to use container views and do something like this:
Here's another picture showing the, rather simplistic, structure of the first View Controller:
The view controller on the left has a container, and then a view which holds the button on top of it. The container indicates that the navigation controller (directly to the right) should appear within itself, that relationship is shown by the =([])=> arrow (formally known as an embed segue). Finally the navigation controller defines its root view controller to the one on the right.
In summary, the first view controller pancakes-in the container view with the button on top, so everything that happens inside has to have the button on top.
Using childViewControllers
aka. The "I hate Storyboards and puppies" mode
Using a similar structure to the Storyboard version, you could create the base view controller with its button, and then, add the view that will become then new "root" of the application, underneath.
To make it clear, let's call the one view controller that holds the button the FakeRootViewController, and the view controller that will be, for all practical purposes, the root of the application: RootViewController. All subsequent view controllers won't even know that there's the FakeRootViewController above everyone else.
FakeRootViewController.m
// The "real" root
#import "RootViewController.h"
// Call once after the view has been set up (either through nib or coded).
- (void)setupRootViewController
{
// Instantiate what will become the new root
RootViewController *root = [[RootViewController alloc] <#initWith...#>];
// Create the Navigation Controller
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:root];
// Add its view beneath all ours (including the button we made)
[self addChildViewController:nav];
[self.view insertSubview:nav.view atIndex:0];
[nav didMoveToParentViewController:self];
}
AppDelegate.m
#import "FakeRootViewController.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
FakeRootViewController *fakeRoot = [[FakeRootViewController alloc] <#initWith...#>];
self.window.rootViewController = fakeRoot;
[self.window makeKeyAndVisible];
return YES;
}
That way, you can have all the benefits of inserting the button on the window, without all the guilt and "Should I really be a programmer?" that it causes.
Potentially you could have 1 main "root" view controller, and all you other view controllers could be child view controllers, with their views as child views. Then they would have their content, and the button would be in the "root" view controller. But this seems just as sketchy and hacky as putting it in the window, and probably less convenient.
I use this button:
#interface UIPopUpButton : UIImageView <UIPopoverControllerDelegate, UIActionSheetDelegate>
{
UIPopoverController* popoverController;
Class popoverClass;
}
- (id) initWithPoint: (CGPoint) point;
- (void) touchesBegan: (NSSet*) touches
withEvent: (UIEvent*) event;
+ (id) buttonAtPoint: (CGPoint) point;
+ (id) buttonAtOriginalPoint;
+ (void) unhighlight;
+ (void) bringButtonToFront;
#property (nonatomic, retain) UIPopoverController* popoverController;
#property (nonatomic, assign) Class popoverClass;
#end
#import "UIPopUpButton.h"
#implementation UIPopUpButton
static UIPopUpButton* button = nil;
static CGPoint originalPoint;
#synthesize popoverClass;
#synthesize popoverController;
+ (id) buttonAtPoint: (CGPoint) point
{
if (button == nil)
{
button = [[UIPopUpButton alloc] initWithPoint: point];
originalPoint = point;
button.popoverClass = [UIPopoverController class];
}
else
{
button.frame = CGRectMake(point.x, point.y, button.frame.size.width, button.frame.size.height);
}
return button;
}
+ (id) buttonAtOriginalPoint
{
return [self buttonAtPoint: originalPoint];
}
+ (void) unhighlight
{
button.highlighted = NO;
}
+ (void) bringButtonToFront
{
[[UIApplication sharedApplication].keyWindow addSubview: [self buttonAtOriginalPoint]];
}
- (id) initWithPoint: (CGPoint) point
{
UIImage* image1 = [UIImage imageNamed: #"topbutton.png"];
UIImage* image2 = [UIImage imageNamed: #"topbutton.png"];
if ((self = [super initWithImage: image1
highlightedImage: image2]))
{
self.userInteractionEnabled = YES;
self.frame = CGRectMake(point.x, point.y, self.frame.size.width, self.frame.size.height);
self.multipleTouchEnabled = NO;
}
return self;
}
- (BOOL) isAppCurrStatus
{
return ([DevToolsClientController sharedInstance].statusOfRootViewController == FrontEndApplication);
}
- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
{
UITouch* touch = [touches anyObject];
if(touch.view == self)
{
if (self.popoverController == nil)
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
UIActionSheet* actionSheet = [[UIActionSheet alloc] initWithTitle: #"Please choice operation:"
delegate: self
cancelButtonTitle: nil
destructiveButtonTitle: nil
otherButtonTitles: nil];
[actionSheet addButtonWithTitle: #"Cancel"];
actionSheet.cancelButtonIndex = 0;
[actionSheet addButtonWithTitle: #"Button 1"];
actionSheet.actionSheetStyle = UIActionSheetStyleDefault;
[actionSheet setTag: 0];
[actionSheet setDelegate: self];
[actionSheet showInView: [self superview]];
[actionSheet release];
[actions release];
}
else
{
PopoverMenuController* contentViewController = [[PopoverMenuController alloc] init];
self.popoverController = [[UIPopoverController alloc] initWithContentViewController: contentViewController];
popoverController.delegate = self;
[popoverController presentPopoverFromRect: CGRectMake(10.0f, 10.0f, 5.0f, 5.0f)
inView: self
permittedArrowDirections: UIPopoverArrowDirectionAny
animated: YES];
contentViewController.popoverController = self.popoverController;
[contentViewController reloadData];
}
}
else
{
[self.popoverController dismissPopoverAnimated:YES];
self.popoverController = nil;
}
}
[super touchesBegan: touches withEvent: event];
}
#pragma mark UIActionSheetDelegate implementation
-(void) actionSheet: (UIActionSheet*) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex
{
NSNumber* indexAction = [[NSNumber alloc] initWithInt: buttonIndex - 1];
}
- (void) runAction: (NSNumber*) indexAction
{
[DevToolsPopoverMenuController runAction: [indexAction integerValue]];
}
#pragma mark -
#pragma mark UIPopoverControllerDelegate implementation
- (void) popoverControllerDidDismissPopover: (UIPopoverController*) thePopoverController
{
if (self.popoverController != nil)
{
self.popoverController = nil;
}
}
- (BOOL) popoverControllerShouldDismissPopover: (UIPopoverController*) thePopoverController
{
//The popover is automatically dismissed if you click outside it, unless you return NO here
return YES;
}
#end
call:
[UIPopUpButton bringButtonToFront];
My button is always on top.
Try subclassing the UIViewController class and make your own one with the button
Create a singleton object that holds the button so all view controllers can reference it and add it to their subview or add it to the window directly.
SomeClass.h
#property (nonatomic) UIButton *yourButton;
+(SomeClass*)sharedSomeClass;
SomeClass.m
#synthesize yourButton = _yourButton;
-(id)init
{
self = [super init];
if(self)
{
_yourButton = [UIButton new];
//Other settings you want for your button
}
return self;
}
+(SomeClass)sharedSomeClass
{
static SomeClass *sharedSomeClass;
if (!sharedSomeClass)
sharedSomeClass = [[super allocWithZone:nil]init];
return sharedSomeClass;
}
+(void)allocWithZone:(NSZone*)zone
{
return [self sharedSomeClass];
}
If you like you can access the window directly like this:
UIWindow *mainwindow = [[[UIApplication sharedApplication]delegate]window];
import SomeClass.h into your view controllers, and access the button from anywhere
#import "SomeClass.h"
SomeClass *someClass = [SomeClass sharedSomeclass];
UIButton *localButton = someClass.yourButton;
I've implemented a custom story board segue which is working fine under iOS 6 but nothing happens in the simulator under iOS 5.1.
The problem is that the popoverController is always nil under iOS 5.1?!
#implementation PopoverFromRectSegue
-(id)initWithIdentifier:(NSString *)identifier
source:(UIViewController *)source
destination:(UIViewController *)destination {
if(self = [super initWithIdentifier:identifier
source:source
destination:destination]) {
}
return self;
}
- (void)perform {
UIPopoverController *popCtrl = ((UIStoryboardPopoverSegue *)self).popoverController;
id controller = [self sourceViewController];
if ([controller isKindOfClass:[UIViewController class]] && [controller respondsToSelector:#selector(popoverRect)]) {
[popCtrl presentPopoverFromRect:[[controller performSelector:#selector(popoverRect)] CGRectValue] inView:[controller view] permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
#end
Any help/hint is appreciated.
Edit:
Just made new sample project. It seems that under iOS5.1 the popoverController is not set
for custom UIStoryBoardSegues.
What else can I do.
The normal popover requires an anchor but a prototype tableView cells is not accepted (failure during compile) and I couldn't find a way to modify the rect the popover is presented from.
Here's my workaround.
PopoverFromRectSegue.h
#interface PopoverFromRectSegue : UIStoryboardPopoverSegue
#property (strong, nonatomic) UIPopoverController *popoverCtrl;
#end
PopoverRectFromSegue.m
#import "PopoverFromRectSegue.h"
#implementation PopoverFromRectSegue
- (void)perform
{
UIPopoverController *popCtrl = ((UIStoryboardPopoverSegue *)self).popoverController;
// under iOS 5.1 the popoverController iVar is not set
// so we have to use our own one
if (nil == popCtrl) {
popCtrl = self.popoverCtrl;
}
id controller = [self sourceViewController];
if ([controller isKindOfClass:[UIViewController class]] && [controller respondsToSelector:#selector(popoverRect)]) {
[popCtrl presentPopoverFromRect:[[controller performSelector:#selector(popoverRect)] CGRectValue] inView:[controller view] permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
}
#end
In my prepareForSegue method I do the following.
if (isPad) {
self.popoverCtrl = [(UIStoryboardPopoverSegue *)segue popoverController];
if (nil == _popoverCtrl) {
self.popoverCtrl = [[UIPopoverController alloc] initWithContentViewController:[segue destinationViewController]];
((PopoverFromRectSegue *)segue).popoverCtrl = _popoverCtrl;
}
self.popoverRect = [NSValue valueWithCGRect:[self.myTableView rectForRowAtIndexPath:indexPath]];
}