0

I am writing a streaming music app and I really like the universal "Now Playing" buttons in Music app and Pandora. I am trying to implement them myself. Unfortunately, the methods I tried led to me confusing myself as well as people who tried to help me. Here's what I've tried so far, all of which don't work/work partially:

  • Using a static UIBarButtonItem in all controllers (works inconsistently, sometimes no segue)
  • Adding a button manually to all controllers in Interface Builder (works, but results in a fade-out/fade-in animation when transitioning)
  • Creating a view controller which had a static UIBarButtonItem from which all controllers subclassed (eliminated the no segue issue from method 1, but still behaves inconsistently)

TL;DR: I don't know how to implement a universal right bar button item. Please tell me how.

Demo Project with a blank representation of my hierarchy.

EDIT: Pandora's developer Neil Mix answered this question. Exactly the same issue. But I did not understand it. It would be great if someone could explain his method.

Community
  • 1
  • 1
duci9y
  • 4,128
  • 3
  • 26
  • 42
  • I think you can use this answer, but not sure. similar problem: http://stackoverflow.com/a/12391120/1633251 – David H Feb 26 '13 at 19:42
  • @DavidH It didn't work. – duci9y Feb 27 '13 at 17:11
  • You did that on the UIButton, right? Well, if this was me I'd create a minimal demo project with the button in it, and add a small bounty, and for sure someone (or more) will provide an answer. It would be easier to play with a project than just guess at it. – David H Feb 27 '13 at 19:14
  • @DavidH Will surely do that when my question becomes eligible for a bounty. Thanks. – duci9y Feb 28 '13 at 08:30
  • @DavidH Woah wait, I did not. I tried to set a `UIBarButtonItem` as a static variable and set it as the right bar button. But that results in inconsistent behaviour. The button is set only sometimes. – duci9y Feb 28 '13 at 14:43
  • If you make it a static item, you can only use it in one view. Instead of doing the above in initWithCoder, did you try moving it to viewDidLoad? – David H Feb 28 '13 at 15:20
  • @DavidH Same thing happens in viewDidLoad. Guess I'll have to wait for the bounty. By the way, my root controller is a tab bar controller, with 5 navigation controllers in it. – duci9y Feb 28 '13 at 15:28
  • Create a simple demo project, post it to DropBox etc, then add an EDIT: statement to your question to where the project is located. I'll look at it tonight but cannot during the work day. – David H Feb 28 '13 at 15:31
  • Please update the question and specify EXACTLY what you want to have happen to the left button when it or the fixed right button is pressed. I assume that you want it to move as a normal button would. Or is the issue the right button??? – David H Mar 01 '13 at 15:17
  • @DavidH I want the left button to behave normally. The issue is with the universal right button. It is universal in every controller, but it fades in and out. I don't want it to fade in or out. – duci9y Mar 01 '13 at 15:33
  • I am totally perplexed - I ran the demo project on the iPhone 6.1 simulator, and also on my iPhone 4, and there is not any flicker or dimming visible of the right bar button. – David H Mar 01 '13 at 17:24
  • I also can't see any unexpectable behaviour.. it's there on every VC subclassing CustomViewController.. – Martin Ullrich Mar 01 '13 at 22:02
  • @DavidH That's because I am now trying to solve it using a static variable. Now, the problem is that the button is set inconsistently. Sometimes it's there, sometimes it's not. I even tried doing it in viewDidLoad as suggested by you, but no go. Sorry if this is becoming confusing. – duci9y Mar 02 '13 at 08:03
  • @MartinUllrich Try performing all possible combinations of transitions. It sometimes isn't there. – duci9y Mar 02 '13 at 08:04
  • @DavidH Everything is super-confusing. I am changing the question. Really sorry about it. – duci9y Mar 02 '13 at 08:13

3 Answers3

3

I implemented a solution for you, it uses a singleton BarButtonManger which is used by a custom UINavigationController-subclass as delegate.
You still have to decide what the target of the button should be (VC navigated to, navigaiton controller, some other singleton / app delegate,..) but this solution should help you: http://cl.ly/3M151o1i3z3O
Dont forget to change the class of the navigation controllers in your storyboard to CustomNavigationController!

//  BarButtonManager.h
#import <UIKit/UIKit.h>

@interface BarButtonManager : NSObject <UINavigationControllerDelegate>

+ (BarButtonManager*)sharedInstance;

@end


//  BarButtonManager.m
#import "BarButtonManager.h"

@implementation BarButtonManager {
    UIBarButtonItem *_sharedItem;
}

+ (BarButtonManager*)sharedInstance
{
    static BarButtonManager *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[BarButtonManager alloc] init];
    });
    return instance;
}


- (UIBarButtonItem*)sharedButtonItem
{
    if (!_sharedItem) { // not thread safe, but let's assume this is only called from UI thread
        UIButton *nowPlayingButton = [UIButton buttonWithType:UIButtonTypeCustom];

        UIImage *background = [[UIImage imageNamed:@"nav-bar-now-playing-button-silver.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(4.0, 7.5, 5.0, 13.0)];
        UIImage *backgroundPressed = [[UIImage imageNamed:@"nav-bar-now-playing-button-pressed-silver.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(4.0, 7.5, 5.0, 13.0)];

        [nowPlayingButton setBackgroundImage:background forState:UIControlStateNormal];
        [nowPlayingButton setBackgroundImage:backgroundPressed forState:UIControlStateHighlighted];

        nowPlayingButton.frame = CGRectMake(0.0, 0.0, background.size.width, background.size.height);

        NSMutableParagraphStyle *centre = [[NSMutableParagraphStyle alloc] init];
        centre.alignment = NSTextAlignmentCenter;
        centre.lineBreakMode = NSLineBreakByWordWrapping;


        NSMutableAttributedString *nowPlayingTitle = [[NSMutableAttributedString alloc] initWithString:@"Now Playing"];
        [nowPlayingTitle addAttributes:@{NSFontAttributeName : [UIFont boldSystemFontOfSize:9.5], NSParagraphStyleAttributeName : centre, NSForegroundColorAttributeName : [UIColor whiteColor]} range:NSMakeRange(0, nowPlayingTitle.length)];


        [nowPlayingButton setAttributedTitle:nowPlayingTitle forState:UIControlStateNormal];

        nowPlayingButton.titleLabel.lineBreakMode = NSLineBreakByCharWrapping;
        nowPlayingButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
        nowPlayingButton.contentVerticalAlignment = UIControlContentVerticalAlignmentTop;
        [nowPlayingButton setTitleEdgeInsets:UIEdgeInsetsMake(0, -3, 0, 5)];

        [nowPlayingButton addTarget:nil action:@selector(nowPlayingPressed) forControlEvents:UIControlEventTouchUpInside];

        _sharedItem = [[UIBarButtonItem alloc] initWithCustomView:nowPlayingButton];
    }
    return _sharedItem;
}

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    navigationController.topViewController.navigationItem.rightBarButtonItem = nil;
    UIBarButtonItem *sharedItem = [self sharedButtonItem];
    sharedItem.target = viewController;
    viewController.navigationItem.rightBarButtonItem = sharedItem;
}

@end


//  CustomNavigationController.h
#import <UIKit/UIKit.h>

@interface CustomNavigationController : UINavigationController

@end

//  CustomNavigationController.m
#import "CustomNavigationController.h"
#import "BarButtonManager.h"

@implementation CustomNavigationController

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        self.delegate = [BarButtonManager sharedInstance];
    }
    return self;
}

@end
Martin Ullrich
  • 94,744
  • 25
  • 252
  • 217
  • I want the NowPlayingController to be instantiated from the storyboard and presented when the button is tapped. How do I do that? – duci9y Mar 02 '13 at 10:35
  • You can still implement the `-nowPlayingPressed` method in CustomViewController. better: implement it in CustomNavigationController and change the `sharedItem.target = viewController` to `sharedItem.target = navigationController` – Martin Ullrich Mar 02 '13 at 11:55
  • I found a better method. Will post it tonight. – duci9y Mar 02 '13 at 12:00
  • I edited your answer. I will accept this if I don't get a better answer in 36 hours. – duci9y Mar 02 '13 at 18:08
2

I recently wrote a blog post about this issue: http://www.codebestowed.com/ios-shared-barbuttonitems

It uses the same strategy as Martin Ullrich but also details how you can set this up in a Storyboard, which requires a bit of a hack. I'll try to summarize here since I know that merely posting links is discouraged in SO.

Basically, the issue is that when you drag a NavigationController into a Storyboard, it doesn't create an editable NavigationBar for you. To get around this, create a ViewController and choose Editor->Embed In->Navigation Controller. The NavigationController that gets created via this method will have a NavigationBar that you can drag BarButtonItems to. Link this new controller to your custom subclass of UINavigationController, and then you can create IBOutlets for anything in the nav bar.

So using a combination of this hack and Martin Ullrich's answer will give you the functionality you want + the ability to use the power of Storyboards.

etipton
  • 164
  • 1
  • 5
0

No much pain. You might have a 'player' view controller. And in app delegate create a variable calendar 'universalPlayer'. At the time you create 'player' object, you should assign this on 'universalPlayer'. Then add bar button to your Navigation controller. On the action of bar button you can simply get the player view controller from your AppDelegate. This is a simple solution.

rakeshNS
  • 4,227
  • 4
  • 28
  • 42