Feb 19

In a previous post I presented a framework for creating a data entry screen. That code focused mainly on UITextFields and allowed the user to move to the next field by tapping the Return key on the keyboard.

What if you have one or more UITextViews on your data entry screen because you want the user to be able to enter multiple lines of text? Well the UITextView handles the Return key internally and adds a new line to the text being entered, just like you would expect when you’re writing text in a text editor. And there is no equivalent of textFieldShouldReturn for UITextView.

If you don’t care about newlines in the entered text and you just want to exit the field when the Return key is tapped, then add the following method to the UITextViewDelegate:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
  BOOL shouldChangeText = YES;
	
  if ([text isEqualToString:@"\n"]) {
    // Find the next entry field  
    BOOL isLastField = YES;
    for (UIView *view in [self entryFields]) {  
      if (view.tag == (textView.tag + 1)) {  
        [view becomeFirstResponder];  
        isLastField = NO;
        break;  
      }  
    }  
    if (isLastField) {
      [textView resignFirstResponder];
    }
	
    shouldChangeText = NO;
  }
	
  return shouldChangeText;
} 

The shouldChangeTextInRange method is called after each keystroke is received by the UITextView, but before it’s shown on screen. If the text is a “\n” character (the Return key) then we either set first responder to the next field, or hide the keyboard in the case that this is the last field. (The entryFields method is explained in the previous post.) Return NO if a return character was detected since we don’t want the new line to show up in the UITextView, otherwise return YES and the text will be processed and shown in the UITextView as normal.

written by Nick \\ tags: ,

May 02

One of the more tedious things in iPhone development is to create functional data entry screens. The challenge is to fit the necessary text fields on the screen and deal with the keyboard that pops up and covers roughly half the screen. There is surprisingly little support for this in the SDK.

Data entry is also one of the more tedious activities for users of your iPhone app, so you should think long and hard about ways to eliminate data entry and make the UI for what you can’t eliminate as user friendly as possible.

This blog post may not offer UI nirvana, but it has some techniques that I’ve found useful on screens with multiple UITextField and UITextView controls. The methods described here are generic enough that they should fit most such screens.

The first step is to create your screen in Interface Builder (or in code if you prefer). The first UI element you need to add is a UIScrollView. It should cover the entire view and be configured to scroll vertically.

Then you place your UITextFields and UITextViews on the UIScrollView. Each data entry control needs to have its delegate set to the view controller so that the controller can act upon events from the controls. In Interface Builder you also configure captitalization, correction, keyboard type, etc for each field. With the exception of the scroll view, this is probably what most of your screens already look like.

Next we’re going to use the versatile “tag” attribute of UIView. The value is tag will be used similar to how tab order is used in other UI technologies. So set the tag to 1 for the data entry field at the top of your screen, 2 for the second, etc.

Now let’s dive into some code. Your controller needs to implement the protocols UITextFieldDelegate and UITextViewDelegate (assuming that you have both types of UI controls in your screen).

@interface MyViewController : UIViewController <UITextFieldDelegate, UITextViewDelegate>

Implement the following methods which are called when a text field or text view becomes the first responder, either by the user tapping inside the field or by your code explicitly requesting it to become the first responder.

#pragma mark UITextFieldDelegate

- (void)textFieldDidBeginEditing:(UITextField *)textField {
	[self scrollViewToCenterOfScreen:textField];
}

#pragma mark UITextViewDelegate

- (void)textViewDidBeginEditing:(UITextView *)textView {
	[self scrollViewToCenterOfScreen:textView];
}

The scrollViewToCenterOfScreen simply takes the frame of the view and positions the scroll view so that the view is in the center of the visible area. The visible area may be significantly reduced by the space taken up by the keyboard. More on that later.

- (void)scrollViewToCenterOfScreen:(UIView *)theView {
	CGFloat viewCenterY = theView.center.y;
	CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];

	CGFloat availableHeight = applicationFrame.size.height - keyboardBounds.size.height;	// Remove area covered by keyboard

	CGFloat y = viewCenterY - availableHeight / 2.0;
	if (y < 0) {
		y = 0;
	}
	scrollView.contentSize = CGSizeMake(applicationFrame.size.width, applicationFrame.size.height + keyboardBounds.size.height);
	[scrollView setContentOffset:CGPointMake(0, y) animated:YES];
}

Notice that the calculations above used a variable, keyboardBounds.size.height, for the height of the keyboard. The lazy approach would be to just use a constant with the value 216. That would work fine in portrait mode with the current keyboards available in the current iPhone OS. But listen to Apple's warnings: Do not hardcode values like this in your code. Your app will break in the future.

One way to get the accurate size of the keyboard is to observe keyboard notifications.

	[[NSNotificationCenter defaultCenter] addObserver:self
											 selector:@selector(keyboardNotification:)
												 name:UIKeyboardWillShowNotification
											   object:nil];

If your screen allows the keyboard to disappear, then you should also observe the UIKeyboardWillHideNotification.

The keyboardNotification method looks like this:

- (void)keyboardNotification:(NSNotification*)notification {
	NSDictionary *userInfo = [notification userInfo];
	NSValue *keyboardBoundsValue = [userInfo objectForKey:UIKeyboardBoundsUserInfoKey];
	[keyboardBoundsValue getValue:&keyboardBounds];
}

And the declaration of  keyboardBounds:

CGRect keyboardBounds;

The above steps ensure that the current field where the user is entering data is made visible. Now for some extra credit let's use the return key on the keyboard to move from one field to the next. This is where the "tag" attribute will be used.

For UITextFields you can implement textFieldShouldReturn like this:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
	// Find the next entry field
	for (UIView *view in [self entryFields]) {
		if (view.tag == (textField.tag + 1)) {
			[view becomeFirstResponder];
			break;
		}
	}

	return NO;
}

What this method does is to look through an array of entry fields to find the one with the next higher tag value. The entryFields method looks like this:

/*
 Returns an array of all data entry fields in the view.
 Fields are ordered by tag, and only fields with tag > 0 are included.
 Returned fields are guaranteed to be a subclass of UIResponder.
 */
- (NSArray *)entryFields {
	if (!entryFields) {
		self.entryFields = [[NSMutableArray alloc] init];
		NSInteger tag = 1;
		UIView *aView;
		while (aView = [self.view viewWithTag:tag]) {
			if (aView && [[aView class] isSubclassOfClass:[UIResponder class]]) {
				[entryFields addObject:aView];
			}
			tag++;
		}
	}
	return entryFields;
}

Taken together you now have a pretty good framework for creating usable data entry screens. Please let me know in the comments any improvements you make or any alternative techniques you've used.

written by Nick \\ tags: , , ,

Apr 07

First you need to add your file to the Resources folder of your Xcode project. Then you can access the file like this (assuming the file is called MyFile.txt):

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"MyFile" ofType:@"txt"];
NSData *myData = [NSData dataWithContentsOfFile:filePath];
if (myData) {
	// do something useful
}

Here’s a complete example reading a help text file into a UIWebView.

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"HelpDoc" ofType:@"htm"];
NSData *htmlData = [NSData dataWithContentsOfFile:filePath];
if (htmlData) {
	[webView loadData:htmlData MIMEType:@"text/html" textEncodingName:@"UTF-8" baseURL:[NSURL URLWithString:@"http://iphoneincubator.com"]];
}

If you want to read the file into a string, which you can then display in a UITextView, for example, then do this:

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"important" ofType:@"txt"];
if (filePath) {
	NSString *myText = [NSString stringWithContentsOfFile:filePath];
	if (myText) {
		textView.text= myText;
	}
}

written by Nick \\ tags: , , ,