Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

iOS SDK: Customizing Popovers

by

Popovers are a great way to display supplementary information in an iPad app. Inevitably, as with most iOS objects, a little customization goes a long way in creating a design that is unique and fits in with your own app. In this tutorial, we will build a basic popover, then explore customizations one at a time, giving you an easy-to-follow path to implement customizations in your own app.


1. Setting Up Your Project

Step 1

Launch Xcode and choose File > New > Project to create a new project. Select an iOS Single View Application and click Next.

Figure 1

Figure 1

Step 2

Fill in the text fields with your project name, organization name, and company identifier. Select iPad from the Devices drop down and make sure the box next to Use Automatic Reference Counting is checked. Leave the boxes for Use Storyboards and Include Unit Tests unchecked and click Next. Choose a location to save your file and click Create.

Figure 2

Figure 2

2. Adding a Navigation Controller

Step 1

Let’s use a navigation controller so that we can add a button to show the popover. Click on AppDelegate.m and find the application:didFinishLaunchingWithOptions: method. Add the following code to create a navigation controller and set it as the root view controller.

UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:self.viewController];
self.window.rootViewController = navController;

Step 2

Now we can add a plus button to the navigation bar. Click on ViewController.m and add the following code to the viewDidLoad method just below [super viewDidLoad];.

UIBarButtonItem *popoverButton = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
                     target:self
                     action:@selector(showPopover:)];
self.navigationItem.rightBarButtonItem = popoverButton;

The UIBarButtonSystemItemAdd creates a plus button; we’ll add it to the right side of the navigation bar. Next we’ll implement the showPopover: method used as the selector.


3. Showing the Popover

Step 1

Before implementing the showPopover: method, let’s add a property for the popover controller. Click on the ViewController.h file and add the following property.

@property (nonatomic, strong) UIPopoverController *popController;

Step 2

Navigate back to the ViewController.m file and declare the showPopover: method in the class extension as demonstrated below.

@interface ViewController ()
- (void)showPopover:(id)sender;
@end

Step 3

Add the following code to define the method under @implementation.

- (void)showPopover:(id)sender
{
    if (self.popController.popoverVisible) {
        [self.popController dismissPopoverAnimated:YES];
        return;
    }

    UIViewController *contentViewController = [[UIViewController alloc] init];
    contentViewController.view.backgroundColor = [UIColor yellowColor];
    UIPopoverController *popController = [[UIPopoverController alloc] initWithContentViewController:contentViewController];
    popController.popoverContentSize = CGSizeMake(300.0f, 600.0f);
    self.popController = popController;
    [self.popController presentPopoverFromBarButtonItem:sender
                    permittedArrowDirections:UIPopoverArrowDirectionUp
                                    animated:YES];
}

First we check to see if the popover is already being shown on the screen. If it is visible, the popover is dismissed and the method returns. If the popover is not being shown on the screen, we create a view controller to be displayed in the popover. Then we create the popover controller and set its size. The last line of code tells the popover controller to present itself from the navigation bar button that was tapped - in this case the plus button -and allows the arrow direction to point up only. One benefit to using this method is that if the user taps another button on the navigation bar, the tap is passed through to the navigation bar.


4. Testing the Standard Popover

At this point, we have implemented a standard popover. Build and run your project and tap the plus button to see a basic popover appear. Let’s take a look at the basics of customizing its appearance.


5. Subclassing UIPopoverBackgroundView

Step 1

In order to customize the appearance of the popover, we’ll need to subclass UIPopoverBackgroundView. Click File > New > File, choose an iOS Cocoa Touch Objective-C Class, and click Next.

Figure 3

Figure 3

Step 2

Name the class PopoverBackgroundView and choose UIPopoverBackgroundView from the Subclass of drop down.

Figure 4

Figure 4

Step 3

There are two UIPopoverBackgroundView properties that need to be overridden. Add the following code to synthesize the arrow direction and arrow offset, overriding the setter and getter methods for these two properties.

@synthesize arrowDirection  = _arrowDirection;
@synthesize arrowOffset     = _arrowOffset;

Step 4

There are three class methods that need to be overridden. Let’s define some values to use with the methods.

#define kArrowBase 30.0f
#define kArrowHeight 20.0f
#define kBorderInset 8.0f

Step 5

Add the code below to override the arrowBase, arrowHeight and contentViewInsets methods.

+ (CGFloat)arrowBase
{
    return kArrowBase;
}

+ (CGFloat)arrowHeight
{
    return kArrowHeight;
}

+ (UIEdgeInsets)contentViewInsets
{
    return UIEdgeInsetsMake(kBorderInset, kBorderInset, kBorderInset, 		kBorderInset);
}

The arrowBase method determines the width of the arrow’s base, while the arrowHeight method determines the height of the arrow. The contentViewInsets method indicates how far from the edge of the background to display the content.

Step 6

Let’s add a background color so we can see the different pieces clearly. Add the following code inside the if statement in the initWithFrame: method.

self.backgroundColor = [UIColor grayColor];

6. Setting the Popover Background View Class Property

Before we can test the popover, we’ll need to import and set the popover controller’s popover background view class property. Click on the ViewController.m file and import the popover background view header file as demonstrated below.

#import "PopoverBackgroundView.h"

While we're still in the ViewController.m file, add the following line of code just below where we created the UIPopoverController in the showPopover: method.

popController.popoverBackgroundViewClass = [PopoverBackgroundView class];

7. Testing the Popover Background View

Build and run the project and tap the plus button to see the popover. You can see that the standard popover has been replaced with the customizations we’ve added so far. The gray border around the popover shows the insets returned from the contentViewInsets method. You can adjust the insets as needed to achieve a desired look. We’ll draw an arrow later in the tutorial to display on the screen.


8. Setting the Shadows and Rounded Corners

The wantsDefaultContentAppearance method determines whether the default inset shadows and rounded corners are displayed in the popover. By returning NO, the popover background view will no longer show the default shadows and rounded corners, allowing you to implement your own. Add the following code to override the method.

+ (BOOL)wantsDefaultContentAppearance
{
    return NO;
}

Build and run the project and you will be able to see the difference.


9. Adding the Arrow

Step 1

We’ll need to create and manage the arrow ourselves; let’s declare a property for an image view that will display the arrow. Add the following code to the class extension.

@property (nonatomic, strong) UIImageView *arrowImageView;

Now we can instantiate the image view. Replace the code inside the if statement in initWithFrame: with the following code.

self.backgroundColor = [UIColor clearColor];

UIImageView *arrowImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
self.arrowImageView = arrowImageView;
[self addSubview:self.arrowImageView];

Step 2

Let’s change the border inset by updating the the kBorderInset defined in PopoverBackgroundView.m with the following code.

#define kBorderInset 0.0f

Step 3

In order to draw the arrow, we’ll need to declare a method to perform the drawing. Add the following method declaration in the class extension in PopoverBackgroundView.m.

- (UIImage *)drawArrowImage:(CGSize)size;

Step 4

Now add the method definition under @implementation.

- (UIImage *)drawArrowImage:(CGSize)size
{
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    [[UIColor clearColor] setFill];
    CGContextFillRect(ctx, CGRectMake(0.0f, 0.0f, size.width, size.height));

    CGMutablePathRef arrowPath = CGPathCreateMutable();
    CGPathMoveToPoint(arrowPath, NULL, (size.width/2.0f), 0.0f);
    CGPathAddLineToPoint(arrowPath, NULL, size.width, size.height);
    CGPathAddLineToPoint(arrowPath, NULL, 0.0f, size.height);
    CGPathCloseSubpath(arrowPath);
    CGContextAddPath(ctx, arrowPath);
    CGPathRelease(arrowPath);

    UIColor *fillColor = [UIColor yellowColor];
    CGContextSetFillColorWithColor(ctx, fillColor.CGColor);
    CGContextDrawPath(ctx, kCGPathFill);

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;

}

Instead of using an imported image, the code above programmatically creates the arrow.

Step 5

Each time the bounds of the popover background view subclass changes, the frame of the arrow needs to be recalculated. We can accomplish this by overriding layoutSubviews. Add the following code to PopoverBackgroundView.m.

- (void)layoutSubviews
{
    [super layoutSubviews];

	CGSize arrowSize = CGSizeMake([[self class] arrowBase], [[self class] arrowHeight]);

    self.arrowImageView.image = [self drawArrowImage:arrowSize];

    self.arrowImageView.frame = CGRectMake(((self.bounds.size.width - arrowSize.width) kBorderInset), 0.0f, arrowSize.width, arrowSize.height);
}

The frame of the arrow’s image view and the arrow’s image are calculated based on the bounds of the popover background view, the border’s insets, and the arrow's base and height.


10. Testing the Popover

Build and run your project to see the customized popover. Even though the border inset is set to zero, the arrow is adjusted to line up with the inside edge of the right inset. By subtracting the border inset when determining the x coordinate for the image view frame for the arrow, we are able to align the image view appropriately.


Conclusion

This tutorial is designed to get you up and running with customizing a popover. There are many directions you can take the project from here; for example, adding another image view to display a custom border image. To do this, you would follow a similar pattern like we did when we created the image view for the arrow. Furthermore, you may want to customize the popover based on the arrow’s direction. In layoutSubviews, a series of if statements could help you test for the arrow’s direction so you could adjust the arrow accordingly. Leave a comment or question below if you have a specific direction you’d like to go with your customizations.

Advertisement