iPad Development : Document Management (part 2)

11/20/2010 3:58:41 PM

2. Creating and Deleting Files

All this time, we've had a button in the lower-right corner of our toolbar just for the purpose of sending our drawing as a PDF in an e-mail message. That's still a nice piece of functionality, but we can do more with that space—namely, replace it with a button that launches a small menu in another popover.

Let's start by creating yet another UIViewController subclass, once again a UITableViewController subclass with no .xib file, named ActionsMenuController. Like some of the other view controllers we've made, this one defines a notification name that's used when the user selects an item in the list it's going to display. It also defines an enumerated type that will show which of the menu items was selected. Here's the entire content of both the .h and .m files:

//  ActionsMenuController.h
#import <UIKit/UIKit.h>
#define ActionsMenuControllerDidSelect @"ActionsMenuControllerDidSelect"
typedef enum SelectedActionType {
NoAction = −1,
} SelectedActionType;
@interface ActionsMenuController : UITableViewController {
SelectedActionType selection;
UIPopoverController *container;
@property (readonly) SelectedActionType selection;
@property (assign, nonatomic) UIPopoverController *container;

// ActionsMenuController.m
#import "ActionsMenuController.h"
@implementation ActionsMenuController
@synthesize selection, container;
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
selection = NoAction;

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
return YES;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)s {
return 5;
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier] autorelease];
switch (indexPath.row) {
case NewDocument:
cell.textLabel.text = @"New Dudel";
case RenameDocument:
cell.textLabel.text = @"Rename this Dudel";
case DeleteDocument:
cell.textLabel.text = @"Delete this Dudel";
case ShowAppInfo:
cell.textLabel.text = @"Dudel App Info";
case EmailPdf:
cell.textLabel.text = @"Send PDF via email";
return cell;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
selection = indexPath.row;
[[NSNotificationCenter defaultCenter]
postNotificationName:ActionsMenuControllerDidSelect object:self];

Switch over to DudelViewController, where we're going to make some slight changes:

Add this to the instance variables:

IBOutlet UIToolbar *toolbar;

Remove this action method:

- (IBAction)touchSendPdfEmailItem:(id)sender;

And add this action method:

- (IBAction)popoverActionsMenu:(id)sender;

Open DudelViewController.xib in Interface Builder, and select the button farthest to the right in the toolbar. Open the Inspector panel, clear out the Title field, and set the Identifier to Action. Then control-drag from the button to DudelViewController, and select the popoverActionsMenu: action. Now save your work, and go back to Xcode.

We'll need to make some changes to DudelViewController.m to match the header file's changes, and to handle this new menu controller. For starters, add this near the top:

#import "ActionsMenuController.h"

Then create the following methods:

- (IBAction)popoverActionsMenu:(id)sender {
ActionsMenuController *amc = [[[ActionsMenuController alloc] initWithNibName:nil
bundle:nil] autorelease];
[self setupNewPopoverControllerForViewController:amc];
amc.container = self.currentPopover;
self.currentPopover.popoverContentSize = CGSizeMake(320, 44*5);
[[NSNotificationCenter defaultCenter] addObserver:self
name:ActionsMenuControllerDidSelect object:amc];
[self.currentPopover presentPopoverFromBarButtonItem:sender
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
- (void)actionsMenuControllerDidSelect:(NSNotification *)notification {
ActionsMenuController *amc = [notification object];
UIPopoverController *popoverController = amc.container;
[popoverController dismissPopoverAnimated:YES];
[self handleDismissedPopoverController:popoverController];
self.currentPopover = nil;
- (void)createDocument {
[self saveCurrentToFile:[FileList sharedFileList].currentFile];
[[FileList sharedFileList] createAndSelectNewUntitled];
dudelView.drawables = [NSMutableArray array];
[dudelView setNeedsDisplay];
- (void)deleteCurrentDocumentWithConfirmation {
[[[[UIAlertView alloc] initWithTitle:@"Delete current Dudel" message:
@"This will remove your current drawing completely. Are you sure you want to do that?"
delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Delete it!", nil]
autorelease] show];
- (void)renameCurrentDocument {
// hold on, we're not quite ready for this yet
- (void)showAppInfo {
// not ready for this one, either!
// UIAlertView delegate method, called by the delete confirmation alert.
// we're only using one UIAlertView right now, so no need to check which
// one this is, just which button was pressed.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
[[FileList sharedFileList] deleteCurrentFile];
[self loadFromFile:[FileList sharedFileList].currentFile];


Then replace this:

- (IBAction)touchSendPdfEmailItem:(id)sender {

with this:

- (void)sendPdfEmail {

We need to add an additional check, which contains another bit of checking all its own, to our handleDismissedPopoverController: method, down near the end of the method (just before the return):

} else if ([popoverController.contentViewController isMemberOfClass:[ActionsMenuController class]]) {
ActionsMenuController *amc = (ActionsMenuController *)popoverController.contentViewController;
switch (amc.selection) {
case NewDocument:
[self createDocument];
case RenameDocument:
[self renameCurrentDocument];
case DeleteDocument:
[self deleteCurrentDocumentWithConfirmation];
case EmailPdf:
[self sendPdfEmail];
case ShowAppInfo:
[self showAppInfo];

Now build and run your app, and try that on for size! You can now both create and delete Dudel documents, and switch between them. Each new document you create will be given a default name containing a timestamp, to ensure its uniqueness. All that's left to do now is to add the ability to rename the files you create here, and to display a brief info panel with another menu item. Both of these will be accomplished using modal displays.

3. Renaming Files

First, let's get the file renaming working. The idea here is that creating a new document should be instantaneous, with the default filename working as a placeholder until the time when the user decides to give it a name. At that point, the user can invoke this functionality through the menu.

Start by making a new UIViewController subclass. This one will not be a UITableViewController subclass, and it needs to have a matching .xib file, where we'll define a simple GUI for renaming a file. Name this controller class FileRenameViewController. Here's the content of the FileRenameViewController.h file:

//  FileRenameViewController.h
#import <UIKit/UIKit.h>
@protocol FileRenameViewControllerDelegate;
@interface FileRenameViewController : UIViewController {
id <FileRenameViewControllerDelegate> delegate;
NSString *originalFilename;
NSString *changedFilename;
IBOutlet UILabel *textLabel;
IBOutlet UITextField *textField;
@property (nonatomic, retain) id <FileRenameViewControllerDelegate> delegate;
@property (nonatomic, copy) NSString *originalFilename;
@property (nonatomic, copy) NSString *changedFilename;
@protocol FileRenameViewControllerDelegate
- (void)fileRenameViewController:(FileRenameViewController *)c
didRename:(NSString *)oldFilename to:(NSString *)newFilename;

One of the new features in iOS 3.2 is the ability to use presentation styles when displaying a modal view. In older versions of iOS, modal views always filled the screen, but here we're going to use a presentation style called UIModalPresentationFormSheet, which presents a 540-by-620 view, centered in the screen, with the rest of screen grayed out. This view slides in from the bottom of the screen, and since it doesn't cover the entire screen, it's slightly less jarring. Let's set it up now.

Open FileRenameViewController.xib in Interface Builder. Select the view, and use the attribute inspector to disable its status bar (as we've done for several other views in Dudel), which will let us resize the view. Use the size inspector to set its size to 540 by 620. Next, use the Library to find a UILabel and a UITextfield, dragging each of them into the view. Switch back to the attribute inspector, and set the font for each of those components to 24-point Helvetica. For the UILabel, also set its # Lines to 0, which will let it display text on multiple lines if necessary. Then make them each nearly fill the width of the view, and position them well above center (in order to leave space for the keyboard), something like what you see in Figure 4. For bonus points, use the attributes inspector to set the text field's placeholder text to Entire Filename.

Figure 4. Laying out GUI components for the FileRenameViewController. Notice the blue resize handles that extend nearly to the sides of the view.

Now all we need to do is connect the textField and textLabel buttons from the File's Owner icon to the appropriate GUI components, and connect the text field's delegate outlet back to File's Owner. Save your .xib. This GUI is done!

Switch back to Xcode, and enter this code for FileRenameViewController.m:

//  FileRenameViewController.m
#import "FileRenameViewController.h"
#import "FileList.h"
@implementation FileRenameViewController
@synthesize delegate;
@synthesize originalFilename;
@synthesize changedFilename;
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
textField.text = [[originalFilename lastPathComponent] stringByDeletingPathExtension];
textLabel.text = @"Please enter a new file name for the current Dudel.";
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[textField becomeFirstResponder];
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
return YES;
- (void)dealloc {

self.delegate = nil;
self.originalFilename = nil;
self.changedFilename = nil;
[super dealloc];
- (void)textFieldDidEndEditing:(UITextField *)tf {
NSString *dirPath = [originalFilename stringByDeletingLastPathComponent];
self.changedFilename = [[dirPath stringByAppendingPathComponent:tf.text]
if ([[FileList sharedFileList].allFiles containsObject:self.changedFilename]) {
textLabel.text =
@"A file with that name already exists! Please enter a different file name.";
} else {
[[FileList sharedFileList] renameFile:self.originalFilename
[delegate fileRenameViewController:self didRename:originalFilename
- (BOOL)textFieldShouldReturn:(UITextField *)tf {
[tf endEditing:YES];
return YES;

Now let's set up DudelViewController to use the new renaming mechanism. Starting in the header, add this:

#import "FileRenameViewController.h"

Then add a protocol to the list of protocols our controller implements:

@interface DudelViewController : UIViewController <ToolDelegate, DudelViewDelegate,
MFMailComposeViewControllerDelegate, UIPopoverControllerDelegate,
FileRenameViewControllerDelegate> {

Switch to the .m file, and fill in this method's body:

- (void)renameCurrentDocument {
FileRenameViewController *controller = [[[FileRenameViewController alloc]
initWithNibName:@"FileRenameViewController" bundle:nil] autorelease];
controller.delegate = self;
controller.modalPresentationStyle = UIModalPresentationFormSheet;
controller.originalFilename = [FileList sharedFileList].currentFile;
[self presentModalViewController:controller animated:YES];

Finally, implement the delegate method that gets called when the view's modal session is done:

- (void)fileRenameViewController:(FileRenameViewController *)c
didRename:(NSString *)oldFilename to:(NSString *)newFilename {
[self dismissModalViewControllerAnimated:YES];

Now build and run your app. You should be able to select a file in the list, and rename it using the Rename this Dudel menu item, as shown in Figure 5.

Figure 5. The FileRenameViewController in action

Other -----------------
- iPad Development : The Split View Concept
- jQuery 1.3 : Developing plugins - Adding new shortcut methods
- jQuery 1.3 : Developing plugins - DOM traversal methods
- Using Cloud Services : Exploring Online Planning and Task Management
- Using Cloud Services : Exploring Online Scheduling Applications
- Using Cloud Services : Exploring Online Calendar Applications
- SOA with .NET and Windows Azure : Service Contracts with WCF (part 3)
- SOA with .NET and Windows Azure : Service Contracts with WCF (part 2)
- SOA with .NET and Windows Azure : Service Contracts with WCF (part 1)
- Cloud Security and Privacy : Data Security and Storage
- iPad SDK : Working with Documents - Desktop Synchronization
- Required Project Images for iPad Apps
- iPhone SDK : GameKit Voice Chat
- iPhone SDK : Creating Basic GameKit Services (part 2) : Sending and Receiving Data
- iPhone SDK : Creating Basic GameKit Services (part 1)
- iPad : Navigating with Maps
- Adding iPad to the Mix
- A Brief History of Legacy .NET Distributed Technologies : .NET Remoting
- A Brief History of Legacy .NET Distributed Technologies : .NET Enterprise Services
- iPad SDK : Outputting to an External Screen
Most View
- Windows Server 2008 : Configuring FTP (part 11) - Managing FTP Firewall Options
- Exchange Server 2010 : Availability Planning for Mailbox Servers (part 4) - DAG Networks
- Search Engine Basics : Vertical Search Engines
- SQL Server 2008 : Client Configuration
- SharePoint 2010 : The Client Object Model (part 3) - Writing the JavaScript WebPart
- Microsoft Visual Studio 2010 : Debugging with Visual Studio 2010 (part 2) - Debugging Threads
- Sharepoint 2010 : Backup and Restore (part 3) - Importing sites, Recovering data from an unattached content database
- Windows 7 : Enhancing Your Browsing Security (part 1) - Blocking Pop-Up Windows
- Troubleshooting Connectivity Between Active Directory and Exchange Server 5.5
- iPad SDK : Keyboard Extensions and Replacements (part 1) - Adding a Keyboard Button in Dudel
Top 10
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 3) - Configuring Recipient Filtering
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 2)
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 1)
- Implementing Edge Services for an Exchange Server 2007 Environment : Installing and Configuring the Edge Transport Server Components
- What's New in SharePoint 2013 (part 7) - BCS
- What's New in SharePoint 2013 (part 6) - SEARCH
- What's New in SharePoint 2013 (part 6) - WEB CONTENT MANAGEMENT
- What's New in SharePoint 2013 (part 5) - ENTERPRISE CONTENT MANAGEMENT
- What's New in SharePoint 2013 (part 4) - WORKFLOWS
- What's New in SharePoint 2013 (part 3) - REMOTE EVENTS