A simple drop down list for iPhone

It is often the case that the controls provided by iOS for the iPhone or iPad don’t quite meet our needs. In this blog we’ll learn how to mimic a simple Drop – Down list using a label, some custom buttons, and a hidden view. So let’s get started!

Open Xcode, choose “Create a new Xcode project,” select the Single View Application template, and click Next. Name the project “DropDownDemo,” and choose options as shown here:

Click Next, choose a location to save the project, and click Create.

When the app template has been created, open the ViewController.zib file, and drag controls to the view as shown below:

The properties for these controls should be set in the following way:

 View: no properties set
◦ Label – Select A Color: Change the text to “Select A Color,” change the background to a light yellow color.
◦ Button: Change the text to “▼” by navigating to Edit | Special Characters in the menu, then selecting the down arrow character. Change the font to System Bold 10.0. Change the background to a light yellow color. The button type is set to Custom.
◦ View: align the left and right sides of the view to the left side of the label and the right side of the button above it. We will also hide this button by checking the “Hidden” checkbox, but not just yet.
▪ Button – Red: set the button type to Custom, the text to “Red,” align the top of the button with the top of its containing view, and the left and right sides of the button with the left and right sides of the view. Set the tag value to 1, and the height of the button to 36.
▪ Button – Blue: as above, but the text is “Blue”, and the tag value is 2
▪ Button – Green: as above, but the text is “Green”, and the tag value is 3

After making these changes, select the view that contains the three buttons (Red, Blue, and Green), and put a check mark in the Hidden property.

Open the ViewController.h file, and add the following properties and methods:

</div>
<div class="objc codesnip"></div>
<div class="objc codesnip"><span class="co1">#import <UIKit/UIKit.h></span><span class="kw1">@interface</span> ViewController <span class="sy0">:</span> UIViewController<span class="kw1">@property</span> <span class="br0">(</span>nonatomic, strong<span class="br0">)</span> IBOutlet UILabel <span class="sy0">*</span>ddText;
<span class="kw1">@property</span> <span class="br0">(</span>nonatomic, strong<span class="br0">)</span> IBOutlet UIView <span class="sy0">*</span>ddMenu;
<span class="kw1">@property</span> <span class="br0">(</span>nonatomic, strong<span class="br0">)</span> IBOutlet UIButton <span class="sy0">*</span>ddMenuShowButton;

<span class="sy0">-</span> <span class="br0">(</span>IBAction<span class="br0">)</span>ddMenuShow<span class="sy0">:</span><span class="br0">(</span>UIButton <span class="sy0">*</span><span class="br0">)</span>sender;
<span class="sy0">-</span> <span class="br0">(</span>IBAction<span class="br0">)</span>ddMenuSelectionMade<span class="sy0">:</span><span class="br0">(</span>UIButton <span class="sy0">*</span><span class="br0">)</span>sender;

<span class="kw1">@end</span>

</source>

&nbsp;

</div>
</div>
In the ViewController.zib file, wire up ddText to the UILabel, ddMenu to the hidden view, and ddMenuShowButton to the small custom button containing the “▼” character. The ddMenuShow method is also wired to this small custom button (Touch Up Inside), and each of the other custom buttons (Red, Blue, and Green) are wired to the ddMenuSelectionMade method, also on TouchUpInside.

Next, open the ViewController.h file, and make changes as shown below:
<div class="codesnip-container">
<div class="objc codesnip"></div>
<div class="objc codesnip"></div>
<div class="objc codesnip"><span class="co1">#import "ViewController.h"</span><span class="kw1">@interface</span> ViewController <span class="br0">(</span><span class="br0">)</span><span class="kw1">@end</span>

<span class="kw1">@implementation</span> ViewController

<span class="kw1">@synthesize</span> ddMenu, ddText;
<span class="kw1">@synthesize</span> ddMenuShowButton;

<span class="sy0">-</span> <span class="br0">(</span>IBAction<span class="br0">)</span>ddMenuShow<span class="sy0">:</span><span class="br0">(</span>UIButton <span class="sy0">*</span><span class="br0">)</span>sender
<span class="br0">{</span>
<span class="kw1">if</span> <span class="br0">(</span>sender.tag <span class="sy0">==</span> 0<span class="br0">)</span> <span class="br0">{</span>
sender.tag <span class="sy0">=</span> <span class="nu0">1</span>;
self.ddMenu.hidden <span class="sy0">=</span> <span class="kw2">NO</span>;
<span class="br0">[</span>sender setTitle<span class="sy0">:</span><span class="co3">@</span><span class="st0">"▲"</span> forState<span class="sy0">:</span>UIControlStateNormal<span class="br0">]</span>;
<span class="br0">}</span> <span class="kw1">else</span> <span class="br0">{</span>
sender.tag <span class="sy0">=</span> <span class="nu0">0</span>;
self.ddMenu.hidden <span class="sy0">=</span> <span class="kw2">YES</span>;
<span class="br0">[</span>sender setTitle<span class="sy0">:</span><span class="co3">@</span><span class="st0">"▼"</span> forState<span class="sy0">:</span>UIControlStateNormal<span class="br0">]</span>;
<span class="br0">}</span>
<span class="br0">}</span>

<span class="sy0">-</span> <span class="br0">(</span>IBAction<span class="br0">)</span>ddMenuSelectionMade<span class="sy0">:</span><span class="br0">(</span>UIButton <span class="sy0">*</span><span class="br0">)</span>sender
<span class="br0">{</span>
self.ddText.text <span class="sy0">=</span> sender.titleLabel.text;
<span class="br0">[</span>self.ddMenuShowButton setTitle<span class="sy0">:</span><span class="co3">@</span><span class="st0">"▼"</span>forState<span class="sy0">:</span>UIControlStateNormal<span class="br0">]</span>;
self.ddMenuShowButton.tag <span class="sy0">=</span> <span class="nu0">0</span>;
self.ddMenu.hidden <span class="sy0">=</span> <span class="kw2">YES</span>;
<span class="kw1">switch</span> <span class="br0">(</span>sender.tag<span class="br0">)</span> <span class="br0">{</span>
<span class="kw1">case</span> 1<span class="sy0">:</span>
self.view.backgroundColor <span class="sy0">=</span> <span class="br0">[</span>UIColor redColor<span class="br0">]</span>;
<span class="kw2">break</span>;
<span class="kw1">case</span> 2<span class="sy0">:</span>
self.view.backgroundColor <span class="sy0">=</span> <span class="br0">[</span>UIColor blueColor<span class="br0">]</span>;
<span class="kw2">break</span>;
<span class="kw1">case</span> 3<span class="sy0">:</span>
self.view.backgroundColor <span class="sy0">=</span> <span class="br0">[</span>UIColor greenColor<span class="br0">]</span>;
<span class="kw2">break</span>;

<span class="kw1">default</span><span class="sy0">:</span>
<span class="kw2">break</span>;
<span class="br0">}</span>
<span class="br0">}</span>
…

</div>
</div>

As always, we begin by synthesizing the properties. After this, we implement our two action methods. The ddMenuShow method shows or hides the menu depending on the current state of its tag. It also changes the direction of the arrow character using the setTitle: forState: method. NSStrings are Unicode strings, so it’s legal to use special characters directly in the string as we’ve done here.

The ddMenuSelectionMade method changes the color of the main view’s background depending on the tag value of the button pushed. It also makes changes to the ddMenuShowButton, and hides the ddMenu view.

Save and run the application.

In a production application, we would certainly want to create a custom UIControl and make the appearance of our drop down box conform to the Human Interface Guidelines. But this technique is a simple way to obtain functionality quickly for testing purposes during the development process.

Have Fun!

 

http://www.edumobile.org/iphone/iphone-programming-tutorials/a-simple-drop-down-list-for-iphone/

Using UIPageControl as a container UIViewController

Setting up the interface

To setup the user interface in Interface Builder, you’ll need to create a UIPageControl and a UIScrollView within a UIViewController, and as many other UIViewControllers as to need.

Setting up the PageViewController class

The PageViewController will contain 2 subview a UIScrollView and a UIPageControl. The UIScrollView will contain the views while the UIPageControl will navigate and control which part of the UIScrollView is visible.
You’ll also need some mechanism for calling addChildViewController: on the view controller, I’m my example I did this by sub-classing PagerViewController, but you could also do this before the view controller is push into view.

So, the public interface should look like this:

@interface PagerViewController : UIViewController 
 
@property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
@property (nonatomic, strong) IBOutlet UIPageControl *pageControl;
 
- (IBAction)changePage:(id)sender;
 
@end

On to the implementation file. First of all, I want to handle all the view appearence at rotation calls myself so, we need to over-ride automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers and return NO indicating we don’t want the UIViewController super class doing this.
When the PagerViewController view comes into view, we will need to signal to the currently active child view controller that it has become visible. So we will forward on the view appeared/disappeared messages.

- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
	return NO;
}
 
- (void)viewDidAppear:(BOOL)animated {
	[super viewDidAppear:animated];
	UIViewController *viewController = [self.childViewControllers objectAtIndex:self.pageControl.currentPage];
	if (viewController.view.superview != nil) {
		[viewController viewDidAppear:animated];
	}
}
 
- (void)viewWillDisappear:(BOOL)animated {
	UIViewController *viewController = [self.childViewControllers objectAtIndex:self.pageControl.currentPage];
	if (viewController.view.superview != nil) {
		[viewController viewWillDisappear:animated];
	}
	[super viewWillDisappear:animated];
}
 
- (void)viewDidDisappear:(BOOL)animated {
	UIViewController *viewController = [self.childViewControllers objectAtIndex:self.pageControl.currentPage];
	if (viewController.view.superview != nil) {
		[viewController viewDidDisappear:animated];
	}
	[super viewDidDisappear:animated];
}

viewWillAppear is a little more complex since we also want to load all the child view controllers into the scroll view (we’ll cover loadScrollViewWithPage next), and also make sure the scroll view’s content size is large enough to handle all the child views.

- (void)viewWillAppear:(BOOL)animated {
	[super viewWillAppear:animated];
 
	for (NSUInteger i =0; i < [self.childViewControllers count]; i++) {
		[self loadScrollViewWithPage:i];
	}
 
	self.pageControl.currentPage = 0;
	_page = 0;
	[self.pageControl setNumberOfPages:[self.childViewControllers count]];
 
	UIViewController *viewController = [self.childViewControllers objectAtIndex:self.pageControl.currentPage];
	if (viewController.view.superview != nil) {
		[viewController viewWillAppear:animated];
	}
 
	self.scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * [self.childViewControllers count], scrollView.frame.size.height);
}

To load the content of the UIViewController into the UIScrolView contentView we go though each child and add its view as a subview of the UIScrollView off-setting the origin by a screen width each time.

- (void)loadScrollViewWithPage:(int)page {
    if (page < 0)
        return;
    if (page >= [self.childViewControllers count])
        return;
 
	// replace the placeholder if necessary
    UIViewController *controller = [self.childViewControllers objectAtIndex:page];
    if (controller == nil) {
		return;
    }
 
	// add the controller's view to the scroll view
    if (controller.view.superview == nil) {
        CGRect frame = self.scrollView.frame;
        frame.origin.x = frame.size.width * page;
        frame.origin.y = 0;
        controller.view.frame = frame;
        [self.scrollView addSubview:controller.view];
    }
}

Handling scrolling

To handle scrolling we need to implement a few UIScrollViewDelegate methods and also the - (IBAction)changePage:(id)sender method we declared earlier.
We need to know how the scrolling occurred, i.e. was it initiated from a gesture swipe across the screen, or by tapping either side of the UIPageControl.

To work this out we update an ivar when the various delegate callback are called..
This code is largely taken from this Cocoa with Love post
I also call the child UIViewController’s viewillAppear etc. methods.

// At the begin of scroll dragging, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
         _pageControlUsed = NO;
}
 
// At the end of scroll animation, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
         _pageControlUsed = NO;
}
 
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
        UIViewController *oldViewController = [self.childViewControllers objectAtIndex:_page];
        UIViewController *newViewController = [self.childViewControllers objectAtIndex:self.pageControl.currentPage];
        [oldViewController viewDidDisappear:YES];
        [newViewController viewDidAppear:YES];
 
        _page = self.pageControl.currentPage;
}

Now, to update the display after the page change we just need implement the scrollViewDidScroll delegate method and the changePage IBAction method.
changing the viewable UIViewController is done by simply scrolling the UIScrollView to the appropriate location.

- (IBAction)changePage:(id)sender {
        int page = ((UIPageControl *)sender).currentPage;
 
        // update the scroll view to the appropriate page
        CGRect frame = self.scrollView.frame;
        frame.origin.x = frame.size.width * page;
        frame.origin.y = 0;
 
        UIViewController *oldViewController = [self.childViewControllers objectAtIndex:_page];
        UIViewController *newViewController = [self.childViewControllers objectAtIndex:self.pageControl.currentPage];
        [oldViewController viewWillDisappear:YES];
        [newViewController viewWillAppear:YES];
 
        [self.scrollView scrollRectToVisible:frame animated:YES];
 
        // Set the boolean used when scrolls originate from the UIPageControl. See scrollViewDidScroll: above.
        _pageControlUsed = YES;
}
 
- (void)scrollViewDidScroll:(UIScrollView *)sender {
    // We don't want a "feedback loop" between the UIPageControl and the scroll delegate in
    // which a scroll event generated from the user hitting the page control triggers updates from
    // the delegate method. We use a boolean to disable the delegate logic when the page control is used.
    if (_pageControlUsed || _rotating) {
        // do nothing - the scroll was initiated from the page control, not the user dragging
        return;
    }
 
    // Switch the indicator when more than 50% of the previous/next page is visible
        CGFloat pageWidth = self.scrollView.frame.size.width;
        int page = floor((self.scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
        if (self.pageControl.currentPage != page) {
                UIViewController *oldViewController = [self.childViewControllers objectAtIndex:self.pageControl.currentPage];
                UIViewController *newViewController = [self.childViewControllers objectAtIndex:page];
                [oldViewController viewWillDisappear:YES];
                [newViewController viewWillAppear:YES];
                self.pageControl.currentPage = page;
                [oldViewController viewDidDisappear:YES];
                [newViewController viewDidAppear:YES];
                _page = page;
        }
}

Finally, Rotation

To handle device rotation, we need to return YES from shouldAutorotateToInterfaceOrientation.
We also need to pass on the following messages to the currently active child UIViewController.

  • willAnimateRotationToInterfaceOrientation:duration:
  • willRotateToInterfaceOrientation:duration:
  • didRotateFromInterfaceOrientation:

But, we also need to handle our own rotation, i.e. resizing the scrollviews contentView, and adjusting the frame of the child subviews, otherwise everything is miss-aligned. This is done inwillAnimateRotationToInterfaceOrientation:duration:, so that the resizing is also animated.

When the frame of UIScrollView adjusts scrollViewDidScroll: also gets called, so to prevent that from flipping us to a different page we set and unset the _rotating flag in the following methods:

  • willRotateToInterfaceOrientation:duration:
  • didRotateFromInterfaceOrientation:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
	return YES;
}
 
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
	UIViewController *viewController = [self.childViewControllers objectAtIndex:self.pageControl.currentPage];
	[viewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
	_rotating = YES;
}
 
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
 
	UIViewController *viewController = [self.childViewControllers objectAtIndex:self.pageControl.currentPage];
	[viewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
 
	self.scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * [self.childViewControllers count], scrollView.frame.size.height);
	NSUInteger page = 0;
	for (viewController in self.childViewControllers) {
		CGRect frame = self.scrollView.frame;
		frame.origin.x = frame.size.width * page;
		frame.origin.y = 0;
		viewController.view.frame = frame;
		page++;
	}
 
	CGRect frame = self.scrollView.frame;
	frame.origin.x = frame.size.width * _page;
	frame.origin.y = 0;
	[self.scrollView scrollRectToVisible:frame animated:NO];
 
}
 
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
	_rotating = NO;
	UIViewController *viewController = [self.childViewControllers objectAtIndex:self.pageControl.currentPage];
	[viewController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}

Conclusion

 

For fully working example project see this repository on GitHub