22

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?

Chris
  • 1,416
  • 18
  • 29
D-Nice
  • 4,772
  • 14
  • 52
  • 86
  • Please post the solution if you find anything. I am stuck with the same situation. –  Jul 20 '13 at 04:38
  • 3
    what do you mean by " During all view transitions, the button should remain static."? Does it means that even when there is a transition from a view, the button should not move? or that the button should be at the same position before and after the transition. – Pochi Jul 24 '13 at 02:12
  • @D-Nice, Have you got the solution??? – iYoung May 26 '15 at 10:19

5 Answers5

42

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:

MAH BUTTON IS PLEASED

Here's another picture showing the, rather simplistic, structure of the first View Controller:

enter image description here

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.

Can
  • 8,502
  • 48
  • 57
  • I'm not using storyboards nor interface builder -- all ui code is being added programatically. I'm have a little trouble understanding the principle behind the image. Is there a container UIView that holds the button and also a subview of all the other content? – D-Nice Jul 20 '13 at 06:56
  • @D-Nice I added another image to explain it better, and I'll update if I can write a all-code example with [childViewControllers](http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html) – Can Jul 20 '13 at 07:06
  • I like this concept. I've implemented it but the way that I understand it, the navigation bar should continue to be managed by `RootViewController.` Any ideas about how to do that? Thanks! – D-Nice Jul 23 '13 at 22:22
  • I don't get what you're asking, you want the `FakeRootViewController` to handle the navigation bar? – Can Jul 23 '13 at 23:06
  • So right now I'm creating `FakeRootViewController` in the AppDelegate, and then following it up with `self.navigationController = [[UINavigationController alloc] initWithRootViewController:fakeRoot];` and then `self.window.rootViewController = self.navigationController;` I get the button overlay, but then I lose the navigation bar I configured it `RootViewController.` I'm trying to figure out how to maintain the original navigation bar that I started with in `RootViewController`, and only keep the button overlay from `FakeRootViewController` – D-Nice Jul 23 '13 at 23:32
  • You need to replicate what's on the storyboard with code: window should present `FakeRootViewController`, then, fake root view controller's child should be a `UINavigationController`, and that navigation controller have the as the root, the `RootViewController`. Try looking at the storyboard image, that's what you're trying to accomplish. – Can Jul 24 '13 at 00:05
  • Sorry, I've never used storyboards and I'm having a little trouble figuring out how to read it. What I'm not understanding is where is the UINavigationController is created? In your code snippet `[self addChildViewController:root];`, the child of `FakeRootViewController` is a UIViewController, not a UINavigationController, correct? So I'm not sure what you mean when you say "the fake root view controller's child should be a `UINavigationController`" Thanks so much! – D-Nice Jul 24 '13 at 00:20
  • Ok, I rewrote the code example to illustrate my point completely. – Can Jul 24 '13 at 01:34
  • Hi can,It is very nice idea...why can't you share one sample for the same...?Please share ASAP. :) – sairam May 30 '14 at 06:48
  • @Can : I have tried to implement it, but I am not able to get the view in my screens. Please help. – iYoung May 26 '15 at 06:53
  • @RajatDeepSingh You might need to write a new question. – Can May 27 '15 at 21:38
1

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.

0

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.

stosha
  • 2,108
  • 2
  • 27
  • 29
0

Try subclassing the UIViewController class and make your own one with the button

Saleh Albuga
  • 448
  • 5
  • 11
0

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;
ksealey
  • 1,698
  • 1
  • 17
  • 16