Search Site

I'm a software engineer and project manager enthusiastic about technology. I've had the opportunity to work on a number of interesting projects over the years.  Here are some thoughts, whitepapers and code folks may find interesting.

Newsletter Signup

Entries in Collections (4)

Saturday
Jun112011

Art Collections 1.5 Update

Since its debut in the App Store I have received some great feedback on Art Collections. Your appreciation has been great! As a result I've developed a new feature I hope folks will take advantage of. I call it Movie Mode.

The goal of Movie Mode is to create a slideshow. Searching and browsing content has provided a good foundation for direct interaction. For the next phase I wanted to focus on indirect usage.

How it Works

Since Collections is always connected to some type of search, Movie Mode displays full-size images from your search thumbnails.  A built-in timer regulates the display and will present each piece in a continuous random order.  As with all Art Collections functionality, Movie Mode works great in both landscape and portrait orientations.  Like the photos app that comes with your iPad, tapping the screen while Movie Mode returns you to the main interface.  Movie Mode works with a few or hundreds of records.

A New Way To Use Your iPad

Movie Mode allows you to use the device in new and interesting ways.  While testing this feature, I placed the device in a freestanding photo stand and let it run continuously during my break (the screen does not lock while in this mode).  The feature also supports multitasking and will automatically "pause" when switched to another app.   

What's Next

With iPad2 support for video mirroring and iOS 5.0 adding support for Airplay, enjoying your favorite art through an HDTV or other connected device will be a reality.  I have interesting plans for Airplay and will be looking into how to apply this to the project. 

 

 

Tuesday
Mar012011

How to Implement a Fading UIView Dialog Window

The great thing about iOS applications is the fit and finish. Although Google's Android OS is available on more devices, the user experience of an iOS or Mac app, if developed correctly, can be amazing.

While developing my own iPad app I've looked to other apps in the hopes of building a fantastic user experience. My conclusion is that a focused effort towards animation and design should allow my project to stand out.

If you've ever set the volume or brightness on a Macbook, iPad or iPhone you have seen the animation I will talk about here. For this article I'll walk through how you can apply the same "fading dialog" technique so your apps look polished and professional. 

Planning and Goals

As seen in the screenshot, my app is tasked with loading images from the Internet.  Since connection speeds vary I've decided to code this as an asynchronous process.  This will allow my main thread to remain free so it can respond to input.  To let users know their request is being processed, the system will show a "loading" dialog window.  Once the content is loaded the window will fade from view.

For months I've toiled with different techniques for mimicking the functionality seen on the Mac OS desktop and iOS.  After several attempts I think I've come up with a great formula and wanted to put it out there for other developers. The following code examples assume that you are using iOS 4.0 or greater. 

Creating a UIView with Rounded Corners

The first thing you'll notice in the screenshot are rounded corners.  There are countless articles are how to make rounded CGRect's and most of them involve complicated Core Graphics (Quartz 2D) code.  With the introduction of iOS 4.0, functionality has been extended to the CALayer class to support edge rounding for layered-backed views. Implementing this requires just two lines of code. 

Notice that modifications are being made to the actual class (self) and not a separate CGRect or CALayer object.  Having a dialog window acting as its own object will provide flexibility when controlling the window from other classes.  The UIView size, text, text shadow and spinner control are configured using Interface Builder (IB). Note - the alpha channel for your view is not modified using IB. Keep reading to see how this works. 

Controlling a UIView dialog window from multiple Windows

Like most iOS apps my project is comprised of UIView subclasses being controlled by a single UIViewController. Since the app loads images using multiple classes, I establish multiple IBOutlets to the window.  Most times the relationship between an outlet and control is 1-1.  In this case we are creating a one-to-many relationship.

Setting the background transparency

Now that we have the plumbing in place the next step involves fine tuning the view transparency. Your first thought may be to set the alpha of your UIView to a percentage (e.g. alpha = 0.5).  This is incorrect.  Why? Remember that the alpha channel for your UIView will dim the entire dialog window.  Since you have white lettering contained in the view, dimming it will cause the entire view to look grey and faded. How does one set the transparency of the view while keeping the text unchanged? The solution lies in setting the UIView background color and not the alpha for the entire view.  

Once the background color is set, the alpha for the entire UIView is set to zero.  Doing so allows the dialog window to be added to the interface when the UIViewController is loaded.  Once loaded it just becomes a simple matter of dialing up the UIView alpha channel using Core Animation when it needs to be displayed. 

Centering the UIView Dialog Window

The following code uses Apple's best practices for centering content on-screen.  Instead of taking a guess at the position you can calculate the centerpoint using the following technique. This is also a good technique for centering content for universal apps (iPad & iPhone).

Making blurry images appear sharp

If you apply the previous technique to center your UIView subclass you may run into an problem with the resulting window appearing blurry (you'll notice it right away if you have the problem).  To correct this, check to ensure that the frame-origin X / Y coordinates are whole integers and are not decimals.  For reasons beyond the scope of this article, using decimal points for content coordinates will cause your content to blur.  This can be corrected by rounding the coordinates to the nearest whole number.

Animating the Display

Now that you have the dialog positioned and looking the way you want, the final piece is to fade the dialog in while something is being loaded.  The code for the actual animation can be accomplished through basic UIView Animation.  Note that when the animation completes, the alpha is set to 1 and not a percentage.  This is because the alpha channel for the UIView background is mutually exclusive to the UIView alpha setting. 

Now that we have everything working, the final trick is fading in the window while another action is taking place. To do this you can call the code to animate the dialog on a background thread. Here is the technique I've applied that works.  Note: If you plan to use this code on your main thread remove the two lines of code related to NSAutorelasePool.   

 Have questions?  Feel free to post them and I'll get back to you. Happy coding!

Tuesday
Sep142010

Using UIScrollView and UIGestureRecoginzer to create a custom interface - Part II

In part one I wrote about planning a custom iPad interface using UIScrollView and UIGestureRecognizer.  Depending on the elements you choose, developing a non-standard interface will probably involve creating a view hierarchy dependent on multiple views as well as Core Graphics.  While detailing each aspect would involve many articles, I've decided to highlight the most important components that make the interface work.

Tip: In this article I make several references to the view hierarchy for MuseumApp.  You can see a graphical representation of this object model in the previous article.

Working with Multiple Views

To keep the pieces organized, my subviews are added into MuseumCollectionView.xib (as objects) then manually configured through code.  The code configuration was a two-step process of declaring IBOutlets then adding them to the appropriate views.   When adding the drawing view (CollectionDrawView) I declared an instance of this directly from my UIViewController (MuseumCollectionView) class.  Once the declaration is established I refresh the draw view by calling setNeedsDisplay. 

 

1:  //  
2: // MuseumCollectionView.h
3: // MuseumApp
4: //
5: // Created by Wayne Bishop on 8/13/10.
6: // Copyright 2010 Arbutus Software. All rights reserved.
7: //
8: #import <UIKit/UIKit.h>
9: #import <QuartzCore/QuartzCore.h>
10: #import "CollectionDrawView.h"
11: #import "MuseumDetailView.h"

12: @interface MuseumCollectionView : UIViewController <UIScrollViewDelegate> {
13: IBOutlet CollectionDrawView *ColDrawView;
14: IBOutlet UIScrollView *scrollView;
15: IBOutlet MuseumDetailView *detailView;
16: }

17: @property(nonatomic, retain) CollectionDrawView *ColDrawView;
18: @property(nonatomic, retain) UIScrollView *scrollView;
19: @property(nonatomic, retain) MuseumDetailView *detailView;

20: //the user selection for the selected collections
21: - (void)selectDetailCollection;
22: - (void)processCollectionFadeAnimation;
23: - (void)setTapGesture;
24: - (void)setPinchGesture;

25: //manage standard input gestures
26: - (IBAction)handleTapGesture:(UIGestureRecognizer *)recognizer;
27: - (IBAction)handlePinchGesture:(UIGestureRecognizer *)recognizer;

28: @end

 

Configuring UIScrollView

With everything hooked up in interface builder (IB) the next step is to configure UIScrollView. For my project an important property to consider is contentSize.  Since I want to enable vertical scrolling I extend the length of the vertical area to a multiple of my drawing view.  This multiple will set the sliding plane for view. The width should match the size of the target view.

 

1:       //sets the scrollable area for the interface  
2: scrollView.contentSize = CGSizeMake(ColDrawView.frame.size.width,
3: ColDrawView.frame.size.height * 1.35);
4: scrollView.clipsToBounds = YES;
5: scrollView.delegate = self;
6: scrollView.pagingEnabled = YES;

 

On line 5 the delegate is set to self.  Even though I am not handling any specific UIScrollView events I keep this code as a placeholder.  On line 6 I set the pageEnabled property so the interface will "glide" to the next set of collections when the user invokes a minor scrolling action.  Adding this property improved the usability of the window and allowed gestures to work more seamlessly.  While hard to visualize, this creates the functional equivalent of paging through apps on the iPad. 

Handling Changing Orientations

MuseumApp is my first iOS application to support multiple orientations. I have to admit this process was cumbersome to get up and running.  There are a handful of things you need to consider that may not be obvious. The first step is to enable views contained within your tab bar controller to change orientation.

If you create a new iPad application based on a single view, that view is configured with the correct boilerplate code. However if you add a UITabBarController to your default UIWindow you must override the default UITabBarController class and provide your own implementation. Here's an example of that code.

 

1:  @implementation MuseumTabController  
2: - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
3: return YES;
4: }
5: @end

 

Processing Orientation Messages

Once the view is capable of receiving orientation messages the next step involves redrawing the display to support images using a 3X3 or 4X2 format.  To make this seamless I need to consider the default orientation (once the app is started) as well as changes in orientation.  For MuseumApp the logic to detect orientation is handled at the UIViewController level.  Once I've confirmed the orientation I set instance properties (e.g. ColDrawView.isPortrait) exposed from my UIView drawRect class and eventually call setNeedsDisplay to refresh the view.

When detecting changes in orientation there are three delegates you can work with.  The one I settled on was (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration.  This function is called when the system detects that a change is about to occur.  In my testing this delegate provided the most natural experience.

 

1:  //gets called before the animation starts. This is a one step animation procedure.
 
2: - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
3: duration:(NSTimeInterval)duration {

4: UIInterfaceOrientation nowOrientation = self.interfaceOrientation;

5: if (nowOrientation == UIInterfaceOrientationPortrait || nowOrientation == UIInterfaceOrientationPortraitUpsideDown) {
6: //moving towards portrait orientation
7: ColDrawView.isPortrait = YES;
8: }

9: else {
10: //moving to landscape orientation
11: ColDrawView.isPortrait = NO;
12: }
13: //process the fade animaton for the rotation
14: [self processCollectionFadeAnimation];
15: }

 

Hopefully everything makes sense with the exception of line #14.  This line ensures that an animation is fired each time the orientation changes.  For MuseumApp this is a simple UIView Fade Transition.  The code (not shown here) fades out the interface while setNeedsDisplay refreshes the drawRect view.  The animation sequence then fades in the view once the animation completes. Applying this technique allows the interface to reorient itself without users really noticing.  I learned this trick in a WWDC presentation and I plan to use it in other areas of the app.

Handling Interface Guestures

The final piece involves adding gesture recognition to the images. When I first started iPhone development I learned a technique of applying hidden UIButtons behind images to enable this type of functionality.  Another technique is to apply the standard touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event.   The problem with this method is that it can only detect touches on the outer most view.  For MuseuemApp this is CollectionDrawView which won't give me the functionality I need.  For it to work I want to detect touches at the UIViewController level.  As a result I changed my implementation to support UIGuestureRecognizer.

 

1:  //register the tap gesture
 
2: - (void)setTapGesture {
3: UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc]
4: initWithTarget:self action:@selector(handleTapGesture:)];
5: singleFingerTap.numberOfTapsRequired = 1;
6: [self.view addGestureRecognizer:singleFingerTap];
7: [singleFingerTap release];
8: }

 

This method is called when my UIControllerView is first loaded.  As you can see the code is pretty straightforward.  On line 3 I set the selector for handling the tap gesture.  Then I set the gesture to support a single tap then add the object to the UIViewController.  Like the iPad photos app I also want images to invoke functionality when they are pinched.  This was an easy process of creating a similar function that supported an instance of UIPinchGuestureRecognizer.

Recognizing Gestures with Images

Now that my UIViewController can detect pinch and tap gestures the final step involves associating gestures with specific images.  For example, if someone taps on the African Collection image they should be taken to a view that displays more information about that specific collection.  Keep in mind that my images are not UIButtons but I need for them to act as buttons.  The following shows the implementation of the tap gesture selector.

 

1:  //handle the tap guesture  
2: - (IBAction)handleTapGesture:(UIGestureRecognizer *)recognizer{

3: CGPoint currentLocation;
4: currentLocation = [recognizer locationInView:self.view];

5: //process the african collection
6: if (CGRectContainsPoint(ColDrawView.AfricanRect, currentLocation)) {

7: //the first collection was selected
8: NSLog(@"The african collection was selected");

9: //process the transition animation to the detail view.
10: [self selectDetailCollection];
11: }
12: }

 

On line 5 I receive the CGPoint (e.g. x and y coordinates) of where the user tapped.  The secret sauce occurs on line 6 where I compare the current tap coordinate with the CGRect of a specific image.  In this case I am detecting if someone tapped the African Collection image.

Conclusion

Building a custom iPad interface involves a fair amount tinkering but the payoff is worth it.  As you move forward with your own solution be sure to test often, and be sure to test all of your animation sequences directly on your iPad (it just works better).  Feel free to post your thoughts about my code and don't be shy about providing additional tips or feedback. Happy coding!

 

Friday
Sep102010

Using UIScrollView and UIGestureRecoginzer to create a custom interface - Part I

If you're learning the iOS platform you may be considering extending your interface building skills beyond the basics. In this article I walk through how you can develop a custom user interface using specialized components such as UIScrollView and UIGestureRecoginzer.

Planning the interface

Because I have a passion for technology in the education space, I am building something that will allow individuals to learn while having fun.  If you take a look at my photographs you'll see that I'm big on outdoor photography and museums.  As a result I've partnered with a well known museum and will be designing an iPad application for their content (codename: MuseumApp). 

When I demonstrate the capabilities of the iPad to friends and colleagues I always start with the built-in photos application.  It's an amazing interface and really shows off the "whiz-bang" features of the platform.  Over time I've noticed that folks are most impressed with interfaces that animate through gestures (e.g. tap, pinch, swipe) or by motion (e.g. rotating the device).  The photos application utilizes these features so I chose to recreate the same core experience for my app.

Design Considerations

A person will open MuseumApp and first see the Collections interface.  This categorizes all the museum's content by major category. Users can explore each collection by tapping or pinching a selected image.  Since I'm building specifically for iPad I also have to ensure my interface supports both portrait and landscape orientations.

With 11 collections in total, only 9 are displayed at any one time in portrait orientation (3 X 3) and 8 in landscape orientation (4 X 2).  For all collections to be seen users will need to scroll the interface.  Finally, when transitioning from one orientation to another I want to ensure collection images reorganize themselves seamlessly - in most cases without users noticing.

The View Hierarchy

Through some trial and error I've come up with a model that works.  Since the entire interface is based on non-UIKit elements the solution needed a bit of creativity. Here's a breakdown of the working components of the custom interface.  The orange objects are those that can be seen when running MuseumApp.  The remaining views work as supporting objects.

 

  • UIWindow - is the default container window when building any iPhone or iPad application.  For my custom interface the configuration for this window was unchanged.
  • UITabBarController - controls the display of other views through tabs.  Since every view in the app will be contained within a tab, the tab bar controller is added to the UIWIndow.  For the purposes of illustration you can see how selecting the search tab will invoke a different view controller.
  • CollectionsView - acts as the main view controller when someone selects the "Collections" tab.  The view controller has an associated XIB (pronounced 'nib') file for maintaining additional views like UIScrollView. 
  • drawRect - is where the logic for the layout and images actually takes place.  With the view controller and scrollview in place I add a basic UIView to UIScrollView and override drawRect.  For the sake of performance the images are saved to the bundle and are drawn as needed using Core Graphics.  The text and white rectangles are also built on the fly.  

Adding Interface Gestures

With the plumbing in place the next step is to make the interface swap views when an image is tapped or pinched.  To make this work I implement pinch and tap UIGestureRecogizers at the CollectionsView level and apply logic to swap views through a basic UIView Animation sequence. 

As a side note UIGestureRecognizer worked perfectly because touch gestures are recognized through  the entire view hierarchy.  This means even though users are touching the outer most view (UIView drawRect) the touch is actually being handled two levels lower at the view controller level - perfect.

Make sense?  This overview explains the basics of the interface but doesn't get into the detail of  supporting multiple orientations or making the actual images touchable.  Read ahead to part II of this series as I explain these details through code. 

Next: Using UIScrollView and UIGestureRecoginzer to create a custom interface - Part II