I'm delving into the new world of UIPageViewControllers and there are a lot of tutorials out there, however all of them seem to create one view, and then just use new instances of it with different content.
I'd really like to be able to create multiple XIBs and then just chain them together with the UIPageViewController but it's too new and I can't get my head around the way it works.
Well, here's a long answer that you should be able to copy and paste. (This code was adapted from Erica Sadun (https://github.com/erica/iOS-5-Cookbook))
First, create a new class of type UIPageViewController. Call it BookController. Now paste the following code in your .h file.
// Used for storing the most recent book page used
#define DEFAULTS_BOOKPAGE #"BookControllerMostRecentPage"
#protocol BookControllerDelegate <NSObject>
- (id) viewControllerForPage: (int) pageNumber;
#optional
- (void) bookControllerDidTurnToPage: (NSNumber *) pageNumber;
#end
#interface BookController : UIPageViewController <UIPageViewControllerDelegate, UIPageViewControllerDataSource>
+ (id) bookWithDelegate: (id) theDelegate;
+ (id) rotatableViewController;
- (void) moveToPage: (uint) requestedPage;
- (int) currentPage;
#property (assign) id <BookControllerDelegate> bookDelegate;
#property (nonatomic, assign) uint pageNumber;
and in your .m file:
#define IS_IPHONE ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
#define SAFE_ADD(_Array_, _Object_) {if (_Object_ && [_Array_ isKindOfClass:[NSMutableArray class]]) [pageControllers addObject:_Object_];}
#define SAFE_PERFORM_WITH_ARG(THE_OBJECT, THE_SELECTOR, THE_ARG) (([THE_OBJECT respondsToSelector:THE_SELECTOR]) ? [THE_OBJECT performSelector:THE_SELECTOR withObject:THE_ARG] : nil)
#pragma Utility Class - VC that Rotates
#interface RotatableVC : UIViewController
#end
#implementation RotatableVC
- (void) loadView
{
[super loadView];
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.view.backgroundColor = [UIColor whiteColor];
}
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
#end
#pragma Book Controller
#implementation BookController
#synthesize bookDelegate, pageNumber;
#pragma mark Debug / Utility
- (int) currentPage
{
int pageCheck = ((UIViewController *)[self.viewControllers objectAtIndex:0]).view.tag;
return pageCheck;
}
#pragma mark Page Handling
// Update if you'd rather use some other decision style
- (BOOL) useSideBySide: (UIInterfaceOrientation) orientation
{
BOOL isLandscape = UIInterfaceOrientationIsLandscape(orientation);
return isLandscape;
}
// Store the new page and update the delegate
- (void) updatePageTo: (uint) newPageNumber
{
pageNumber = newPageNumber;
[[NSUserDefaults standardUserDefaults] setInteger:pageNumber forKey:DEFAULTS_BOOKPAGE];
[[NSUserDefaults standardUserDefaults] synchronize];
SAFE_PERFORM_WITH_ARG(bookDelegate, #selector(bookControllerDidTurnToPage:), [NSNumber numberWithInt:pageNumber]);
}
// Request controller from delegate
- (UIViewController *) controllerAtPage: (int) aPageNumber
{
if (bookDelegate &&
[bookDelegate respondsToSelector:#selector(viewControllerForPage:)])
{
UIViewController *controller = [bookDelegate viewControllerForPage:aPageNumber];
controller.view.tag = aPageNumber;
return controller;
}
return nil;
}
// Update interface to the given page
- (void) fetchControllersForPage: (uint) requestedPage orientation: (UIInterfaceOrientation) orientation
{
BOOL sideBySide = [self useSideBySide:orientation];
int numberOfPagesNeeded = sideBySide ? 2 : 1;
int currentCount = self.viewControllers.count;
uint leftPage = requestedPage;
if (sideBySide && (leftPage % 2)) leftPage--;
// Only check against current page when count is appropriate
if (currentCount && (currentCount == numberOfPagesNeeded))
{
if (pageNumber == requestedPage) return;
if (pageNumber == leftPage) return;
}
// Decide the prevailing direction by checking the new page against the old
UIPageViewControllerNavigationDirection direction = (requestedPage > pageNumber) ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse;
[self updatePageTo:requestedPage];
// Update the controllers
NSMutableArray *pageControllers = [NSMutableArray array];
SAFE_ADD(pageControllers, [self controllerAtPage:leftPage]);
if (sideBySide)
SAFE_ADD(pageControllers, [self controllerAtPage:leftPage + 1]);
[self setViewControllers:pageControllers direction: direction animated:YES completion:nil];
}
// Entry point for external move request
- (void) moveToPage: (uint) requestedPage
{
[self fetchControllersForPage:requestedPage orientation:(UIInterfaceOrientation)[UIDevice currentDevice].orientation];
}
#pragma mark Data Source
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
[self updatePageTo:pageNumber + 1];
return [self controllerAtPage:(viewController.view.tag + 1)];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
[self updatePageTo:pageNumber - 1];
return [self controllerAtPage:(viewController.view.tag - 1)];
}
#pragma mark Delegate
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
NSUInteger indexOfCurrentViewController = 0;
if (self.viewControllers.count)
indexOfCurrentViewController = ((UIViewController *)[self.viewControllers objectAtIndex:0]).view.tag;
[self fetchControllersForPage:indexOfCurrentViewController orientation:orientation];
BOOL sideBySide = [self useSideBySide:orientation];
self.doubleSided = sideBySide;
UIPageViewControllerSpineLocation spineLocation = sideBySide ? UIPageViewControllerSpineLocationMid : UIPageViewControllerSpineLocationMin;
return spineLocation;
}
-(void)dealloc{
self.bookDelegate = nil;
}
#pragma mark Class utility routines
// Return a UIViewController that knows how to rotate
+ (id) rotatableViewController
{
UIViewController *vc = [[RotatableVC alloc] init];
return vc;
}
// Return a new book
+ (id) bookWithDelegate: (id) theDelegate
{
BookController *bc = [[BookController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
bc.dataSource = bc;
bc.delegate = bc;
bc.bookDelegate = theDelegate;
return bc;
}
This Class can now be used to control any book you create in any project, and for multiple books in a single project. For each book, create a delegate UIPageViewController with the #interface:
#interface NameOfBookController : UIPageViewController <BookControllerDelegate>
In the .m file of this delegate, include:
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
[super loadView];
CGRect appRect = [[UIScreen mainScreen] applicationFrame];
self.view = [[UIView alloc] initWithFrame: appRect];
self.view.backgroundColor = [UIColor whiteColor];
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
// Establish the page view controller
bookController = [BookController bookWithDelegate:self];
bookController.view.frame = (CGRect){.size = appRect.size};
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
// Add the child controller, and set it to the first page
[self.view addSubview:bookController.view];
[self addChildViewController:bookController];
[bookController didMoveToParentViewController:self];
}
Then add:
- (id) viewControllerForPage: (int) pageNumber
{
// Establish a new controller
UIViewController *controller;
switch (pageNumber) {
case 0:
view1 = [[FirstViewController alloc] init];
view1.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
controller = view1;
//rinse and repeat with each new view controller
break;
case 1:
//etc.
break;
default:
return nil;
break;
}
return controller;
}
For the record, this code is not memory-safe. If using ARC, add in your #autoreleasePool{}; if not, don't forget your retain/release cycle.
I hope this helps!
This article shows how to create an app using UIPageViewController with custom viewcontrollers for each page: http://www.informit.com/articles/article.aspx?p=1760500&seqNum=6
Related
From Apple's sample code and reading the docs I can see no way configuring the NSPathControl to behave similarly to e.g. the 'jump bar' in the Xcode Editor window:
I.e. have it represent a path (or other kind of hierarchy) and make each component of the path a clickable popup to navigate the hierarchy..?
Anybody having luck faking such behaviour using a NSPathControlDelegate listening to clicks and showing a menu in a temporary window?
Seems like a common design where one would even expect some OSS implementation - but no such luck yet googling for it..
I made a subclass of NSPathControl so that I can use mouseDown: to popup the context menus of the component cells at the right position. I added also a delegate to the menu to create deeper menus on demand.
- (void)mouseDown:(NSEvent *)event {
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSPathCell *cell = self.cell;
NSPathComponentCell *componentCell = [cell pathComponentCellAtPoint:point
withFrame:self.bounds
inView:self];
NSRect componentRect = [cell rectOfPathComponentCell:componentCell
withFrame:self.bounds
inView:self];
NSMenu *menu = [componentCell menuForEvent:event
inRect:componentRect
ofView:self];
if (menu.numberOfItems > 0) {
NSUInteger selectedMenuItemIndex = 0;
for (NSUInteger menuItemIndex = 0; menuItemIndex < menu.numberOfItems; menuItemIndex++) {
if ([[menu itemAtIndex:menuItemIndex] state] == NSOnState) {
selectedMenuItemIndex = menuItemIndex;
break;
}
}
NSMenuItem *selectedMenuItem = [menu itemAtIndex:selectedMenuItemIndex];
[menu popUpMenuPositioningItem:selectedMenuItem
atLocation:NSMakePoint(NSMinX(componentRect) - 17, NSMinY(componentRect) + 2)
inView:self];
}
}
- (NSMenu *)menuForEvent:(NSEvent *)event {
if (event.type != NSLeftMouseDown) {
return nil;
}
return [super menuForEvent:event];
}
I extended Stephan's answer slightly to accommodate for lazily loading the menu items. I created a small protocol to call for the menu rather than having to build the menu's ahead of time for each cell:
NSPathControlExtended.h
#protocol NSPathControlExtendedDelegate <NSPathControlDelegate>
#required
- (NSMenu *)pathControl:(NSPathControl *)pathControl menuForCell:(NSPathComponentCell *)cell;
#end
#interface NSPathControlExtended : NSPathControl
#property (weak) id <NSPathControlExtendedDelegate> delegate;
#end
NSPathControlExtended.m
#import "NSPathControlExtended.h"
#implementation NSPathControlExtended
#synthesize delegate;
- (instancetype)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
- (void)mouseDown:(NSEvent *)event {
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSPathCell *cell = self.cell;
NSPathComponentCell *componentCell = [cell pathComponentCellAtPoint:point
withFrame:self.bounds
inView:self];
NSRect componentRect = [cell rectOfPathComponentCell:componentCell
withFrame:self.bounds
inView:self];
NSMenu *menu = [delegate pathControl:self menuForCell:componentCell];
if (menu.numberOfItems > 0) {
NSUInteger selectedMenuItemIndex = 0;
for (NSUInteger menuItemIndex = 0; menuItemIndex < menu.numberOfItems; menuItemIndex++) {
if ([[menu itemAtIndex:menuItemIndex] state] == NSOnState) {
selectedMenuItemIndex = menuItemIndex;
break;
}
}
NSMenuItem *selectedMenuItem = [menu itemAtIndex:selectedMenuItemIndex];
[menu popUpMenuPositioningItem:selectedMenuItem
atLocation:NSMakePoint(NSMinX(componentRect) - 17, NSMinY(componentRect) + 2)
inView:self];
}
}
- (NSMenu *)menuForEvent:(NSEvent *)event {
if (event.type != NSLeftMouseDown) {
return nil;
}
return [super menuForEvent:event];
}
#end
My inspiration for this project is the Droplr and CloudApp mac menubar applications. I have been attempting to implement the code explained here.
However, when I implement the code, the menubar images disappear. Here is my code to create the status item:
- (id)init {
self = [super init];
if (self != nil) {
// Install status item into the menu bar
NSStatusItem *statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:STATUS_ITEM_VIEW_WIDTH];
_statusItemView = [[StatusItemView alloc] initWithStatusItem:statusItem];
_statusItemView.image = [NSImage imageNamed:#"Status"];
_statusItemView.alternateImage = [NSImage imageNamed:#"StatusHighlighted"];
_statusItemView.action = #selector(togglePanel:);
StatusItemView* dragView = [[StatusItemView alloc] initWithFrame:NSMakeRect(0, 0, 24, 24)];
[statusItem setView:dragView];
[dragView release];
}
return self;
}
This is my view file:
#import "StatusItemView.h"
#implementation StatusItemView
#synthesize statusItem = _statusItem;
#synthesize image = _image;
#synthesize alternateImage = _alternateImage;
#synthesize isHighlighted = _isHighlighted;
#synthesize action = _action;
#synthesize target = _target;
#pragma mark -
- (id)initWithStatusItem:(NSStatusItem *)statusItem {
CGFloat itemWidth = [statusItem length];
CGFloat itemHeight = [[NSStatusBar systemStatusBar] thickness];
NSRect itemRect = NSMakeRect(0.0, 0.0, itemWidth, itemHeight);
self = [super initWithFrame:itemRect];
if (self != nil) {
_statusItem = statusItem;
_statusItem.view = self;
}
return self;
}
#pragma mark -
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
//register for drags
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
}
return self;
}
//we want to copy the files
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
return NSDragOperationCopy;
}
perform the drag and log the files that are dropped
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];
if( [[pboard types] containsObject:NSFilenamesPboardType] ) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
NSLog(#"Files: %#",files);
}
return YES;
}
#pragma mark -
- (void)drawRect:(NSRect)dirtyRect {
[self.statusItem drawStatusBarBackgroundInRect:dirtyRect withHighlight:self.isHighlighted];
NSImage *icon = self.isHighlighted ? self.alternateImage : self.image;
NSSize iconSize = [icon size];
NSRect bounds = self.bounds;
CGFloat iconX = roundf((NSWidth(bounds) - iconSize.width) / 2);
CGFloat iconY = roundf((NSHeight(bounds) - iconSize.height) / 2);
NSPoint iconPoint = NSMakePoint(iconX, iconY);
[icon compositeToPoint:iconPoint operation:NSCompositeSourceOver];
}
#pragma mark -
#pragma mark Mouse tracking
- (void)mouseDown:(NSEvent *)theEvent {
[NSApp sendAction:self.action to:self.target from:self];
}
#pragma mark -
#pragma mark Accessors
- (void)setHighlighted:(BOOL)newFlag {
if (_isHighlighted == newFlag) return;
_isHighlighted = newFlag;
[self setNeedsDisplay:YES];
}
#pragma mark -
- (void)setImage:(NSImage *)newImage {
if (_image != newImage) {
_image = newImage;
[self setNeedsDisplay:YES];
}
}
- (void)setAlternateImage:(NSImage *)newImage {
if (_alternateImage != newImage) {
_alternateImage = newImage;
if (self.isHighlighted) {
[self setNeedsDisplay:YES];
}
}
}
#pragma mark -
- (NSRect)globalRect {
NSRect frame = [self frame];
frame.origin = [self.window convertBaseToScreen:frame.origin];
return frame;
}
#end
Thanks everyone!
I know this is an old question but perhaps this might help:
Try setting the NSStatusItem *statusItem as a #propery of the class. If you have ARC, the menubar might be getting destroyed right after the init function finishes.
I am trying to add the buttons I create to an array and then remove them buttons from the array. My array keeps returning null so I get the feeling my buttons are not even being added to my array?
I am a beginner. I am using Xcode 4.3. Here is my code:
//
// MainViewController.h
// Test-Wards
//
// Created by Dayle Pearson on 5/12/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import "FlipsideViewController.h"
#interface MainViewController : UIViewController <FlipsideViewControllerDelegate>
{
/*This stuff creates a timer */
IBOutlet UILabel *opponentsBlue;
NSTimer *timer;
int redBlue;
/*Stuff for making a label creator */
CGPoint startPoint;
int xStuff, yStuff;
/*array for storing wards*/
NSMutableArray *wardArray;
}
#property CGPoint startPoint;
- (IBAction)startRedBlue:(id)sender;
- (IBAction)removeWard:(id)
sender;
- (void)countdown;
#end
//
// MainViewController.m
// Test-Wards
//
// Created by Dayle Pearson on 5/12/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import "MainViewController.h"
#interface MainViewController ()
#end
#implementation MainViewController
#synthesize startPoint;
- (void)countdown
{
if (redBlue < 2) {
[timer invalidate];
timer = nil;
}
redBlue -= 1;
opponentsBlue.text = [NSString stringWithFormat:#"%i", redBlue];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *theTouch = [touches anyObject];
startPoint = [theTouch locationInView:self.view];
}
- (IBAction)startRedBlue:(id)sender
{
UIButton *wardButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
wardButton.frame = CGRectMake((startPoint.x - 5), (startPoint.y - 5), 10, 10);
[wardButton setTitle:#"180" forState:UIControlStateNormal];
//add targets and actions
/*[wardButton addTarget:self action:#selector() forControlEvents:<#(UIControlEvents)#>*/
//add to a view
[self.view addSubview:wardButton];
[self->wardArray addObject: wardButton];
NSLog(#"This elemnt = %#", wardArray);
}
- (IBAction)removeWard:(id)sender
{
[self->wardArray removeLastObject];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#pragma mark - Flipside View
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller
{
[self dismissModalViewControllerAnimated:YES];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showAlternate"]) {
[[segue destinationViewController] setDelegate:self];
}
}
#end
You forgot to initialize your wardArray. You should add
wardArray = [NSMutableArray array];
to your designated initializer.
In Objective-C sending messages to nil objects is legal - these messages are simply ignored. That's why you do not see the items that you added.
I also noticed that you add buttons to the view, but you never remove them. To remove the buttons from the screen, change the code as follows:
- (IBAction)removeWard:(id)sender
{
[[self->wardArray lastObject] removeFromSuperview];
[self->wardArray removeLastObject];
}
You have to initialize your array before you can add to it
wardArray = [NSMutableArray array]; // quick and easy
wardArray = [[NSMutableArray alloc] init]; // oldschool
I recommend doing it like so in your method. This will only initialize it once if it doesn't exist so theres no chance of it never being ready to have objects :)
- (IBAction)startRedBlue:(id)sender
{
....
// If wardArray doesn't exist we create it. Otherwise we add our ward to it.
if (!wardArray) {
wardArray = [NSMutableArray array];
} else {
[self->wardArray addObject: wardButton];
}
}
- (IBAction)removeWard:(id)sender
{
UIButton *ward = (UIButton *)sender;
[ward removeFromSuperview]; // Takes it off the screen.
[self->wardArray removeObject:ward]; //Takes it out of the array
}
I'm working on a soundboard app, that has several pages of buttons to play sound effects with a stop button on every page should the user wish to manually interrupt the clip. I'm using avaudioplayer in each view to play the sound upon pressing the button for that clip. It works fine until the view is changed. If a user jumps to a new page the sound keeps playing and the stop button stops working even if they return to the original view. Pressing a sound button no longer interrupts the running sound resulting in two sounds over each other.
From googling and searching this site, I know the issue is that each view change creates a new instance of the player and the remedy is to create a singleton class. Unfortunately I have yet to find any further examples of how to actually do this. If someone could provide or point the way to a beginners guide for creating an avaudioplayer singleton I would really appreciate it. All I need to be able to do is pass the file name to the shared player and start play with a sound clip button and have the stop button stop sounds no matter what view the user is on. I am using the ios 5.1 sdk with storyboards and ARC enabled.
My solution, as used in one of my own projects, is posted beneath. Feel free to copy-and-paste, I intend to open-source this project once it's finished :)
A preview of the player can be seen on YouTube: http://www.youtube.com/watch?v=Q98DQ6iNTYM
AudioPlayer.h
#protocol AudioPlayerDelegate;
#interface AudioPlayer : NSObject
#property (nonatomic, assign, readonly) BOOL isPlaying;
#property (nonatomic, assign) id <AudioPlayerDelegate> delegate;
+ (AudioPlayer *)sharedAudioPlayer;
- (void)playAudioAtURL:(NSURL *)URL;
- (void)play;
- (void)pause;
#end
#protocol AudioPlayerDelegate <NSObject>
#optional
- (void)audioPlayerDidStartPlaying;
- (void)audioPlayerDidStartBuffering;
- (void)audioPlayerDidPause;
- (void)audioPlayerDidFinishPlaying;
#end
AudioPlayer.m
// import AVPlayer.h & AVPlayerItem.h
#interface AudioPlayer ()
- (void)playerItemDidFinishPlaying:(id)sender;
#end
#implementation AudioPlayer
{
AVPlayer *player;
}
#synthesize isPlaying, delegate;
+ (AudioPlayer *)sharedAudioPlayer
{
static dispatch_once_t pred;
static AudioPlayer *sharedAudioPlayer = nil;
dispatch_once(&pred, ^
{
sharedAudioPlayer = [[self alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:sharedAudioPlayer selector:#selector(playerItemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
});
return sharedAudioPlayer;
}
- (void)playAudioAtURL:(NSURL *)URL
{
if (player)
{
[player removeObserver:self forKeyPath:#"status"];
[player pause];
}
player = [AVPlayer playerWithURL:URL];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
if (delegate && [delegate respondsToSelector:#selector(audioPlayerDidStartBuffering)])
[delegate audioPlayerDidStartBuffering];
}
- (void)play
{
if (player)
{
[player play];
if (delegate && [delegate respondsToSelector:#selector(audioPlayerDidStartPlaying)])
[delegate audioPlayerDidStartPlaying];
}
}
- (void)pause
{
if (player)
{
[player pause];
if (delegate && [delegate respondsToSelector:#selector(audioPlayerDidPause)])
[delegate audioPlayerDidPause];
}
}
- (BOOL)isPlaying
{
DLog(#"%f", player.rate);
return (player.rate > 0);
}
#pragma mark - AV player
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == player && [keyPath isEqualToString:#"status"])
{
if (player.status == AVPlayerStatusReadyToPlay)
{
[self play];
}
}
}
#pragma mark - Private methods
- (void)playerItemDidFinishPlaying:(id)sender
{
DLog(#"%#", sender);
if (delegate && [delegate respondsToSelector:#selector(audioPlayerDidFinishPlaying)])
[delegate audioPlayerDidFinishPlaying];
}
#end
AudioPlayerViewController.h
extern NSString *const kAudioPlayerWillShowNotification;
extern NSString *const kAudioPlayerWillHideNotification;
#interface AudioPlayerViewController : UIViewController
#property (nonatomic, assign, readonly) BOOL isPlaying;
#property (nonatomic, assign, readonly) BOOL isPlayerVisible;
- (void)playAudioAtURL:(NSURL *)URL withTitle:(NSString *)title;
- (void)pause;
#end
AudioPlayerViewController.m
NSString *const kAudioPlayerWillShowNotification = #"kAudioPlayerWillShowNotification";
NSString *const kAudioPlayerWillHideNotification = #"kAudioPlayerWillHideNotification";
#interface AudioPlayerViewController () <AudioPlayerDelegate>
#property (nonatomic, strong) AudioPlayerView *playerView;
- (void)playButtonTouched:(id)sender;
- (void)closeButtonTouched:(id)sender;
- (void)hidePlayer;
#end
#implementation AudioPlayerViewController
#synthesize playerView, isPlaying, isPlayerVisible;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
playerView = [[AudioPlayerView alloc] initWithFrame:CGRectZero];
[AudioPlayer sharedAudioPlayer].delegate = self;
}
return self;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
self.view = playerView;
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
[playerView.playButton addTarget:self action:#selector(playButtonTouched:) forControlEvents:UIControlEventTouchUpInside];
[playerView.closeButton addTarget:self action:#selector(closeButtonTouched:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Private methods
- (AudioPlayerView *)playerView
{
return (AudioPlayerView *)self.view;
}
- (void)hidePlayer
{
[[NSNotificationCenter defaultCenter] postNotificationName:kAudioPlayerWillHideNotification object:nil];
[self.playerView hidePlayer];
}
- (void)playButtonTouched:(id)sender
{
DLog(#"play / pause");
if ([AudioPlayer sharedAudioPlayer].isPlaying)
{
[[AudioPlayer sharedAudioPlayer] pause];
}
else
{
[[AudioPlayer sharedAudioPlayer] play];
}
[self.playerView showPlayer];
}
- (void)closeButtonTouched:(id)sender
{
DLog(#"close");
if ([AudioPlayer sharedAudioPlayer].isPlaying)
[[AudioPlayer sharedAudioPlayer] pause];
[self hidePlayer];
}
#pragma mark - Instance methods
- (void)playAudioAtURL:(NSURL *)URL withTitle:(NSString *)title
{
playerView.titleLabel.text = title;
[[AudioPlayer sharedAudioPlayer] playAudioAtURL:URL];
[[NSNotificationCenter defaultCenter] postNotificationName:kAudioPlayerWillShowNotification object:nil];
[playerView showPlayer];
}
- (void)pause
{
[[AudioPlayer sharedAudioPlayer] pause];
[[NSNotificationCenter defaultCenter] postNotificationName:kAudioPlayerWillHideNotification object:nil];
[playerView hidePlayer];
}
#pragma mark - Audio player delegate
- (void)audioPlayerDidStartPlaying
{
DLog(#"did start playing");
playerView.playButtonStyle = PlayButtonStylePause;
}
- (void)audioPlayerDidStartBuffering
{
DLog(#"did start buffering");
playerView.playButtonStyle = PlayButtonStyleActivity;
}
- (void)audioPlayerDidPause
{
DLog(#"did pause");
playerView.playButtonStyle = PlayButtonStylePlay;
}
- (void)audioPlayerDidFinishPlaying
{
[self hidePlayer];
}
#pragma mark - Properties
- (BOOL)isPlaying
{
return [AudioPlayer sharedAudioPlayer].isPlaying;
}
- (BOOL)isPlayerVisible
{
return !playerView.isPlayerHidden;
}
#end
AudioPlayerView.h
typedef enum
{
PlayButtonStylePlay = 0,
PlayButtonStylePause,
PlayButtonStyleActivity,
} PlayButtonStyle;
#interface AudioPlayerView : UIView
#property (nonatomic, strong) UIButton *playButton;
#property (nonatomic, strong) UIButton *closeButton;
#property (nonatomic, strong) UILabel *titleLabel;
#property (nonatomic, strong) UIActivityIndicatorView *activityView;
#property (nonatomic, assign) PlayButtonStyle playButtonStyle;
#property (nonatomic, assign, readonly) BOOL isPlayerHidden;
- (void)showPlayer;
- (void)hidePlayer;
#end
AudioPlayerView.m
#implementation AudioPlayerView
{
BOOL _isAnimating;
}
#synthesize playButton, closeButton, titleLabel, playButtonStyle, activityView, isPlayerHidden = _playerHidden;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"musicplayer_background.png"]];
_playerHidden = YES;
activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
activityView.frame = CGRectMake(0.0f, 0.0f, 30.0f, 30.0f);
[self addSubview:activityView];
playButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 30.0f, 30.0f)];
[playButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[playButton setBackgroundImage:[UIImage imageNamed:#"button_pause.png"] forState:UIControlStateNormal];
playButton.titleLabel.textAlignment = UITextAlignmentCenter;
[self addSubview:playButton];
closeButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 30.0f, 30.0f)];
[closeButton setBackgroundImage:[UIImage imageNamed:#"button_close.png"] forState:UIControlStateNormal];
[closeButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
closeButton.titleLabel.textAlignment = UITextAlignmentCenter;
[self addSubview:closeButton];
titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 240.0f, 30.0f)];
titleLabel.text = nil;
titleLabel.textAlignment = UITextAlignmentCenter;
titleLabel.font = [UIFont boldSystemFontOfSize:13.0f];
titleLabel.numberOfLines = 2;
titleLabel.textColor = [UIColor whiteColor];
titleLabel.backgroundColor = [UIColor clearColor];
[self addSubview:titleLabel];
}
return self;
}
- (void)layoutSubviews
{
#define PADDING 5.0f
DLog(#"%#", NSStringFromCGRect(self.bounds));
CGRect frame = self.bounds;
CGFloat y = frame.size.height / 2;
titleLabel.center = CGPointMake(frame.size.width / 2, y);
CGFloat x = titleLabel.frame.origin.x - (playButton.frame.size.width / 2) - PADDING;
playButton.center = CGPointMake(x, y);
activityView.center = CGPointMake(x, y);
x = titleLabel.frame.origin.x + titleLabel.frame.size.width + (closeButton.frame.size.width / 2) + PADDING;
closeButton.center = CGPointMake(x, y);
}
#pragma mark - Instance methods
- (void)showPlayer
{
if (_isAnimating || _playerHidden == NO)
return;
_isAnimating = YES;
[UIView
animateWithDuration:0.5f
animations:^
{
CGRect frame = self.frame;
frame.origin.y -= 40.0f;
self.frame = frame;
}
completion:^ (BOOL finished)
{
_isAnimating = NO;
_playerHidden = NO;
}];
}
- (void)hidePlayer
{
if (_isAnimating || _playerHidden)
return;
_isAnimating = YES;
[UIView
animateWithDuration:0.5f
animations:^
{
CGRect frame = self.frame;
frame.origin.y += 40.0f;
self.frame = frame;
}
completion:^ (BOOL finished)
{
_isAnimating = NO;
_playerHidden = YES;
}];
}
- (void)setPlayButtonStyle:(PlayButtonStyle)style
{
playButton.hidden = (style == PlayButtonStyleActivity);
activityView.hidden = (style != PlayButtonStyleActivity);
switch (style)
{
case PlayButtonStyleActivity:
{
[activityView startAnimating];
}
break;
case PlayButtonStylePause:
{
[activityView stopAnimating];
[playButton setBackgroundImage:[UIImage imageNamed:#"button_pause.png"] forState:UIControlStateNormal];
}
break;
case PlayButtonStylePlay:
default:
{
[activityView stopAnimating];
[playButton setBackgroundImage:[UIImage imageNamed:#"button_play.png"] forState:UIControlStateNormal];
}
break;
}
[self setNeedsLayout];
}
#end
AppDelegate - didFinishLaunching
// setup audio player
audioPlayer = [[AudioPlayerViewController alloc] init]; // public property ...
CGRect frame = self.window.rootViewController.view.frame;
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
CGFloat tabBarHeight = tabBarController.tabBar.frame.size.height;
audioPlayer.view.frame = CGRectMake(0.0f, frame.size.height - tabBarHeight, 320.0f, 40.0f);
[self.window.rootViewController.view insertSubview:audioPlayer.view belowSubview:tabBarController.tabBar];
From any view controller inside the app I start audio with the following code:
- (void)playAudioWithURL:(NSURL *)URL title:(NSString *)title
{
OnsNieuwsAppDelegate *appDelegate = (OnsNieuwsAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.audioPlayer playAudioAtURL:URL withTitle:title];
}
Assets
For the above example, the following assets can be used (button images are white, so hard to see against background):
Buttons:
Background:
There's a lot of discussion (and links to blogs, etc.) about singletons over at What should my Objective-C singleton look like?, and I see a fair number of tutorials as a result of this Google search: http://www.google.com/search?q=+cocoa+touch+singleton+tutorial, but the real answer to your question, I believe, is that you should do one of two things:
If you do want the sound for a particular view to continue playing when the user switches, create the player as you're doing now, but when the view (re)appears, check that a player exists, and don't make a new one.
If you want the sound to stop, then stop the sound when the view changes (i.e., in viewWillDisappear:).
I followed tutorial from cocos2d official site . I try to create some items for a menu when creating them i pass a selector with one parameter. For each item i pass different selector . I think here is the problem , but i dont see realy why here is the problem. My header file looks :
// When you import this file, you import all the cocos2d classes
#import "cocos2d.h"
#import "CCTouchDispatcher.h"
// HelloWorldLayer
#interface HelloWorldLayer : CCLayer {
CCSprite *first;
CCSprite *second;
}
// returns a CCScene that contains the HelloWorldLayer as the only child
+(CCScene *) scene;
- (void) setUpMenus;
- (void) doSomethingOne: (CCMenuItem *) menuItem;
- (void) doSomethingTwo: (CCMenuItem *) menuItem;
- (void) doSomethingThree: (CCMenuItem *) menuItem;
#end
Implementation file :
// Import the interfaces
#import "HelloWorldLayer.h"
// HelloWorldLayer implementation
#implementation HelloWorldLayer
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
- (void) doSomethingOne: (CCMenuItem *) menuItem
{
NSLog(#"The first menu was called");
}
- (void) doSomethingTwo: (CCMenuItem *) menuItem
{
NSLog(#"The second menu was called");
}
- (void) doSomethingThree: (CCMenuItem *) menuItem
{
NSLog(#"The third menu was called");
}
// on "init" you need to initialize your instance
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super" return value
if( (self=[super init])) {
first = [CCSprite spriteWithFile:#"seeker.png"];
first.position = ccp(100, 100);
[self addChild:first];
second = [CCSprite spriteWithFile:#"Icon.png"];
second.position = ccp(50, 50);
[self addChild:second];
[self schedule:#selector(nextFrame:)];
[self setUpMenus];
self.isTouchEnabled = YES;
}
return self;
}
- (void) registerWithTouchDispatcher {
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
- (BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
return YES;
}
- (void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint location = [self convertTouchToNodeSpace: touch];
[second stopAllActions];
[second runAction: [CCMoveTo actionWithDuration:1 position:location]];
}
- (void) nextFrame:(ccTime)dt {
first.position = ccp( first.position.x + 100*dt, first.position.y );
if (first.position.x > 480+32) {
first.position = ccp( -32, first.position.y );
}
}
- (void) setUpMenus {
CCMenuItemImage *menuItem1 = [CCMenuItemImage itemFromNormalImage:#"myfirstbutton.png"
selectedImage:#"myfirstbutton_selected.png"
target:self
selector:#selector(doSomenthingOne:)];
CCMenuItemImage *menuItem2 = [CCMenuItemImage itemFromNormalImage:#"mysecondbutton.png"
selectedImage:#"mysecondbutton_selected.png"
target:self
selector:#selector(doSomenthingTwo:)];
CCMenuItemImage *menuItem3 = [CCMenuItemImage itemFromNormalImage:#"mythirdbutton.png"
selectedImage:#"mythirdbutton_selected.png"
target:self selector:#selector(doSomenthingThree:)];
CCMenu *myMenu = [CCMenu menuWithItems:menuItem1,menuItem2,menuItem3, nil];
[myMenu alignItemsVertically];
[self addChild:myMenu];
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
[super dealloc];
}
#end
You've got the same typo in all three menu item creation calls. You're telling the menu items that the selector they should use is called doSomenthing... (note the spurious n in the middle):
CCMenuItemImage *menuItem1 = [... selector:#selector(doSomenthingOne:)];
CCMenuItemImage *menuItem2 = [... selector:#selector(doSomenthingTwo:)];
CCMenuItemImage *menuItem3 = [... selector:#selector(doSomenthingThree:)];
but the actual names of your methods are doSomethingOne:, doSomethingTwo:, and doSomethingThree:.
The exact cause of the error message is that later, when the menu item needs to perform that selector, it will ask your class to tell it the method signature for the selector you gave it. Since you gave the item an incorrect selector, your class doesn't know the signature, and it returns nil. The menu item tries to construct an NSInvocation object anyways to perform its action, which fails because the invocation can't be created with a nil signature.
Fix the typos and everything should work fine.