/* |
File: PickListController.m |
|
Contains: Runs a pick list table view. |
|
Written by: DTS |
|
Copyright: Copyright (c) 2011 Apple Inc. All Rights Reserved. |
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. |
("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of |
these terms. If you do not agree with these terms, please do |
not use, install, modify or redistribute this Apple software. |
|
In consideration of your agreement to abide by the following |
terms, and subject to these terms, Apple grants you a personal, |
non-exclusive license, under Apple's copyrights in this |
original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or |
without modifications, in source and/or binary forms; provided |
that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the |
following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks |
or logos of Apple Inc. may be used to endorse or promote |
products derived from the Apple Software without specific prior |
written permission from Apple. Except as expressly stated in |
this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or |
by other works in which the Apple Software may be incorporated. |
|
The Apple Software is provided by Apple on an "AS IS" basis. |
APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING |
WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, |
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING |
THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
|
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, |
INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY |
OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY |
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR |
OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF |
SUCH DAMAGE. |
|
*/ |
|
#import "PickListController.h" |
|
@interface PickListController () <UITableViewDataSource, UITableViewDelegate> |
@property (nonatomic, retain, readwrite) UIView * topView; |
@property (nonatomic, retain, readwrite) UITableView * tableView; |
@end |
|
@implementation PickListController |
|
@synthesize pickList = _pickList; |
|
@synthesize debug = _debug; |
@synthesize delegate = _delegate; |
|
@synthesize topView = _topView; |
@synthesize tableView = _tableView; |
|
- (id)initWithPickList:(NSArray *)pickList |
// See comment in header. |
{ |
assert(pickList != nil); |
assert([pickList count] != 0); |
self = [super init]; |
if (self != nil) { |
self->_pickList = [pickList copy]; |
assert(self->_pickList != nil); |
} |
return self; |
} |
|
- (id)initWithContentsOfFile:(NSString *)pickListFilePath |
// See comment in header. |
{ |
NSArray * pickListArray; |
|
assert(pickListFilePath != nil); |
|
pickListArray = [NSArray arrayWithContentsOfFile:pickListFilePath]; |
assert([pickListArray isKindOfClass:[NSArray class]]); |
|
return [self initWithPickList:pickListArray]; |
} |
|
- (id)initWithPickListNamed:(NSString *)pickListName bundle:(NSBundle *)bundle |
// See comment in header. |
{ |
NSString * pickListPath; |
|
assert(pickListName != nil); |
// bundleName may be nil |
|
if (bundle == nil) { |
bundle = [NSBundle bundleForClass:[self class]]; |
assert(bundle != nil); |
} |
|
pickListPath = [bundle pathForResource:pickListName ofType:@"plist"]; |
assert(pickListPath != nil); |
|
return [self initWithContentsOfFile:pickListPath]; |
} |
|
- (void)dealloc |
{ |
[self->_pickList release]; |
assert(self->_topView == nil); |
assert(self->_tableView == nil); |
[super dealloc]; |
} |
|
#pragma mark * Attach and detach |
|
- (CGRect)tableViewFrame |
// Calculates the frame for the pick list based on the coordinates of the top |
// view and the size of the containing view (that is, the top view's superview, |
// which will also be the pick list's superview). |
{ |
CGRect topViewFrame; |
CGRect containerViewBounds; |
CGRect frame; |
|
topViewFrame = self.topView.frame; |
containerViewBounds = self.topView.superview.bounds; |
|
frame.origin.x = containerViewBounds.origin.x; |
frame.origin.y = topViewFrame.origin.y + topViewFrame.size.height; |
frame.size.height = containerViewBounds.size.height - (topViewFrame.origin.y + topViewFrame.size.height); |
frame.size.width = containerViewBounds.size.width; |
|
return frame; |
} |
|
- (void)attachBelowView:(UIView *)topView |
// See comment in header. |
{ |
assert(topView != nil); |
|
assert(self.topView == nil); // don't try and attach twice! |
self.topView = topView; |
|
assert(self.tableView == nil); |
|
// Create and configure the table view and add it to the view hierarchy. |
|
self.tableView = [[[UITableView alloc] initWithFrame:[self tableViewFrame] style:UITableViewStylePlain] autorelease]; |
|
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; |
if (self.debug) { |
self.tableView.backgroundColor = [UIColor yellowColor]; |
} |
|
self.tableView.dataSource = self; |
self.tableView.delegate = self; |
|
// We place the view /under/ the top view. This is necessary so that the top view masks |
// any new table view cells as they are being inserted at the top of the table (in the case |
// where we do an animated grow of the table in response to a keyboard hide). If you change |
// the YES to a NO, the new cells show on top of the top view, resulting in a very ugly effect. |
|
if (YES) { |
[self.topView.superview insertSubview:self.tableView belowSubview:self.topView]; |
} else { |
[self.topView.superview addSubview:self.tableView]; |
} |
|
[self.tableView flashScrollIndicators]; |
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow: ) name:UIKeyboardDidShowNotification object:nil]; |
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; |
} |
|
- (void)detach |
// See comment in header. |
{ |
if (self.tableView != nil) { |
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil]; |
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; |
|
self.topView = nil; |
|
self.tableView.delegate = nil; |
self.tableView.dataSource = nil; |
[self.tableView removeFromSuperview]; |
self.tableView = nil; |
} |
} |
|
#pragma mark * Keyboard handling |
|
- (BOOL)beginAnimationFromKeyboardNotification:(NSNotification *)note |
// Start an animated block based on the keyboard animation information |
// in the notification. Returns NO if the animation information is |
// missing (such as after a rotate). |
{ |
BOOL animated; |
NSDictionary * userInfo; |
NSNumber * durationObj; |
NSNumber * curveObj; |
NSTimeInterval duration; |
UIViewAnimationCurve curve; |
|
userInfo = [note userInfo]; |
assert(userInfo != nil); |
|
animated = NO; |
|
durationObj = (NSNumber *) [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey]; |
curveObj = (NSNumber *) [userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey ]; |
|
if ( (durationObj != nil) && (curveObj != nil) ) { |
assert([durationObj isKindOfClass:[NSNumber class]]); |
assert([curveObj isKindOfClass:[NSNumber class]]); |
|
duration = (NSTimeInterval) [durationObj doubleValue]; |
curve = (UIViewAnimationCurve) [curveObj unsignedIntegerValue]; |
|
[UIView beginAnimations:[note name] context:nil]; |
[UIView setAnimationCurve:curve]; |
[UIView setAnimationDuration:duration]; |
|
animated = YES; |
} |
|
return animated; |
} |
|
- (void)keyboardDidShow:(NSNotification *)note |
// A notification callback, called when the keyboard is shown. We recalculate |
// the size of the pick list table view to fit between the bottom of the top |
// view and the top of the keyboard. |
{ |
NSDictionary * userInfo; |
CGRect keyboardFrameEnd; // screen coordinates |
CGRect keyboardFrameEndInContainerViewCoordinates; // in the coordinate space of the view that contains |
CGRect newTableViewFrame; // the table view (and the top view for that matter) |
BOOL animated; |
|
userInfo = [note userInfo]; |
assert(userInfo != nil); |
|
assert([[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] isKindOfClass:[NSValue class]]); |
keyboardFrameEnd = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; |
|
keyboardFrameEndInContainerViewCoordinates = [self.tableView.superview convertRect:keyboardFrameEnd fromView:nil]; |
|
if (self.debug) { |
NSLog(@"-keyboardDidShow:"); |
NSLog(@" keyboard in screen coordinates %@", NSStringFromCGRect(keyboardFrameEnd)); |
NSLog(@"keyboard in container coordinates %@", NSStringFromCGRect(keyboardFrameEndInContainerViewCoordinates)); |
NSLog(@" table view bounds %@", NSStringFromCGRect(self.tableView.bounds)); |
} |
|
if ( CGRectIntersectsRect(self.tableView.frame, keyboardFrameEndInContainerViewCoordinates) ) { |
newTableViewFrame = self.tableView.frame; |
newTableViewFrame.size.height = keyboardFrameEndInContainerViewCoordinates.origin.y - newTableViewFrame.origin.y; |
|
if (self.debug) { |
NSLog(@" table view frame start %@", NSStringFromCGRect(self.tableView.frame)); |
NSLog(@" table view frame end %@", NSStringFromCGRect(newTableViewFrame)); |
} |
animated = [self beginAnimationFromKeyboardNotification:note]; |
self.tableView.frame = newTableViewFrame; |
if (animated) { |
[UIView commitAnimations]; |
} |
[self.tableView flashScrollIndicators]; |
} |
} |
|
- (void)keyboardWillHide:(NSNotification *)note |
// A notification callback, called when the keyboard is hidden. We revert the |
// pick list table view to its default size. |
{ |
#pragma unused(note) |
CGRect newTableViewFrame; |
BOOL animated; |
|
newTableViewFrame = [self tableViewFrame]; |
|
if (self.debug) { |
NSLog(@"-keyboardWillHide:"); |
NSLog(@" table view frame start %@", NSStringFromCGRect(self.tableView.frame)); |
NSLog(@" table view frame end %@", NSStringFromCGRect(newTableViewFrame)); |
} |
animated = [self beginAnimationFromKeyboardNotification:note]; |
self.tableView.frame = newTableViewFrame; |
if (animated) { |
[UIView commitAnimations]; |
} |
[self.tableView flashScrollIndicators]; |
} |
|
#pragma mark * Table view callbacks |
|
- (NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger)section |
{ |
#pragma unused(tv) |
#pragma unused(section) |
assert(tv == self.tableView); |
assert(section == 0); |
|
return [self.pickList count]; |
} |
|
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath |
{ |
#pragma unused(tv) |
#pragma unused(indexPath) |
UITableViewCell * cell; |
|
assert(tv == self.tableView); |
assert(indexPath != nil); |
assert(indexPath.section == 0); |
assert(indexPath.row < [self.pickList count]); |
|
cell = [self.tableView dequeueReusableCellWithIdentifier:@"cell"]; |
if (cell == nil) { |
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"] autorelease]; |
assert(cell != nil); |
|
cell.textLabel.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]]; |
cell.textLabel.numberOfLines = 2; |
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap; |
} |
cell.textLabel.text = [self.pickList objectAtIndex:indexPath.row]; |
|
return cell; |
} |
|
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath |
{ |
#pragma unused(tv) |
assert(tv == self.tableView); |
assert(indexPath != nil); |
assert(indexPath.section == 0); |
assert(indexPath.row < [self.pickList count]); |
|
if ([self.delegate respondsToSelector:@selector(pickList:didPick:)]) { |
// It's likely that the client is going to release its reference to us in |
// response to to this callback, so we ensure that our reference to self |
// persists while everything shuts down. |
// |
// This also means we don't have to copy the item from the pickList array; we |
// retain that array until we're deallocated, and we aren't be deallocated until |
// the autorelease pool drains. |
|
[[self retain] autorelease]; |
|
[self.delegate pickList:self didPick:[self.pickList objectAtIndex:indexPath.row]]; |
} |
|
[self.tableView deselectRowAtIndexPath:indexPath animated:YES]; |
} |
|
@end |