iAppExperience

making beautiful apps

4 notes &

DYFloatingHeaderView - A Facebook inspired floating header view

Facebook’s recent attempt to “revert” its official iOS client from HTML5 to a native iOS app seems to have been very well received (well, at least by the iOS community, not sure how the HTML folks think). The new app is now much faster and reliable than before, and although this update is primarily an engineering rework, it does come with a few really nice UI/UX improvements, and the floating header in the home view is one of them.

The following is a screenshot that shows how the new header looks like when it shows:

image

As we can see from the above screenshot, the header contains three buttons: Status, Photo and Check In. The interesting thing with this header is its location changes as the underlying table (an UITableView, or to be specific, a subclass of UIScrollView) scrolls. When the underlying table first shows, the header floats right on top of the table. When user scrolls up and down, the header shows and hides itself in an elegant way, which both makes way for an optimal screen space for viewing table contents when needed, and allows easy access to the buttons that it contains. What a smart design! How can we implement this in Objective-C?

Feeling inspired, I sat down and started to “decode” the header in my head. About one hour later, DYFloatingHeaderView was created.

How to use

Like other open source iOS controls that I created, DYFloatingHeaderView is very simply to integrate:

[Updated Sep. 18th, 2012] As Sun Long pointed out in his comment, there is a flaw in the original design that by setting scrollView.delegate to an instance of DYFloatingHeaderView, the original delegate will no longer be accessed. The new version has this issue fixed, although it needs a few more steps to integrate, as I’ll outline below.

  • Step 1: Download the source from Github, unzip, copy and include DYFloatingHeaderView.m and DYFloatingHeaderView.h into your project

  • Step 2: Since technically the header is not a subview of the table view (otherwise it will scroll together with the table, which makes it very hard if not impossible to maintain its own position), we cannot simply create and reference DYFloatingHeaderView within an UITableViewController. Instead, what we need to do is to make the header a sibling of the table. In order to do this, all we need to do is to create a class (if it doesn’t exist yet) that serves as a container view for both the header and the table. e.g. In Xcode, create a new UIViewController subclass HomeViewController, and add the following lines of code in viewDidLoad:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // Floating header
        DYFloatingHeaderView *floatingHeader = [[DYFloatingHeaderView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, [DYFloatingHeaderView height])];
        [self.view addSubview:floatingHeader];
    
        // Table view
        self.listViewController = [[SampleListViewController alloc] init];
        self.listViewController.view.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height);
        self.listViewController.headerView = floatingHeader;
        [self.view insertSubview:self.listViewController.view belowSubview:floatingHeader];
    
    }
    
  • Step 3: In SampleListViewController, declare a DYFloatingHeaderView property, and update scrollView’s insets (this is needed to allow the scrollView to display contents without being blocked by the header):

    // In SampleListViewController.h
    @property(nonatomic, strong) DYFloatingHeaderView *headerView;
    

    And then in SampleListViewController.m:

    @synthesize headerView = _headerView;
    
    - (void)setHeaderView:(DYFloatingHeaderView *)headerView {
        _headerView = headerView;
        [_headerView updateScrollViewInsets:(UIScrollView *) self.view];
    }
    
  • Step 4: Still in SampleListViewController, override the following methods defined in UIScrollViewDelegate, and call the corresponding DYFloatingHeaderView method in each:

    // In SampleListViewController.m
    #pragma mark - UIScrollViewDelegate
    
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        [self.headerView scrollViewDidScroll:scrollView];
    }
    
    - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
        [self.headerView scrollViewWillBeginDragging:scrollView];
    }
    
    - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
        [self.headerView scrollViewWillBeginDecelerating:scrollView];
    }
    
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
        [self.headerView scrollViewDidEndDecelerating:scrollView];
    }
    

    There you should have it!

DYFloatingHeaderView in action

The following is a very short YouTube video that I made to demonstrate how DYFloatingHeaderView works in action!

As always, you are welcome to leave a comment below if you have any questions or suggestions. I’d love to know if you use it in your own projects. If you like DYFloatingHeaderView or any other open source components that I created, please consider following me on Twitter. :)

Filed under iphone iOS OSS ObjectiveC

3 notes &

How to create a Jamie’s Recipes style segment control?

A few days ago, I wrote about how one can possibly create the eye-catching strikeout effect in Jamie’s Recipes iOS application. (Remember that cool animation?) As I mentioned in that post, Jamie’s Recipes is such a great application that there are so many details we can learn from. So today I’m going to show you another example on creating one of the UI effects in the app - the beautiful segment control.

picture

To me, the thing that amazes me in this segment control’s design is the animation that slides each segment up or down when I tap on any of the segments. After the animation is performed, a highlight will gently appear on top of the selected segment, which not only looks visually appealing, but also reminds me which segment is currently in focus. Isn’t that amazing?

So how can we create it?

Background images

The first question that I have on my mind is, are those segment buttons’ background static images? This question is especially important because when the segment button is active it looks different. If that is not from a static image then there must be some drawing techniques involved.

So the first thing I do is to take a look at the images extracted from the app. (If you wonder how to do this, I highly recommend that you read Peter Boctor’s wonderful tutorial Extracting images from Apps in the AppStore) There I find several pairs of images with names like recipe_tab_1.png, recipe_tab_1@2x.png, recipe_tab_1_active.png, recipe_tab_1_active@2x.png, etc. OK. That gives us the answer - the segment buttons’ background (both highlighted and not) come from static images, which is rather easy to deal with.

UIButton or UIView?

The next step is to figure out how to use the above mentioned images in order to create a custom segment control. There are quite a few tutorials and/or open source projects on the internet that tells us how to make a custom segment control. The basic idea behind most of these projects is to create an UIButton subclass for each segment, set up its properties, and hook them up together in a way that looks and behaves similar to the standard UISegmentControl. I’ll take this approach. But when it comes to the parent class of the segment buttons, I decide to not inherit from UIButton, but instead from UIView. This is because it’s much easier to animate an UIView subclass and manipulate its highlight/non-highlight state without having an UIButton that tries to be too clever. You’ll see how simple it is to work with an UIView subclass in the next part.

Animation

This is probably the most exciting part of the segment control’s implementation. (yes, writing good quality apps is exciting, otherwise why bother?) We can add images into UIImageViews, and then animate the change of UIImageViews’ frames using [UIImage animateWithDuration: delay: options: animations: completion:] (what a long name).

One of the nice things about UIImageView is that it can take two images, one for each state:

self.imageView = [[UIImageView alloc] initWithImage:self.normalImage highlightedImage:self.highlightImage];

There is also a highlighted property defined on UIImageView, which makes it very easy to change state whenever we want:

- (void)slideDownSegmentButtonToHighlight {
    [UIView animateWithDuration:ANIMATION_DURATION delay:0                           options:UIViewAnimationOptionCurveEaseIn animations:^{
        self.imageView.frame = CGRectOffset(self.imageView.frame, 0, TOP_OFFSET);
    }                completion:^void(BOOL finished) {
        self.imageView.highlighted = YES;
    }];
}

The above code slides a segment button down by animating the change of the button’s imageView’s frame. When the animation is completed, this code simply changes the highlight state of the imageView so that a different image (in this case, a static highlight image) is displayed. How simple is that.

Simulate UISegmentControl’s behavior

There’s one more thing to figure out: At anytime, if one segment button is highlighted, then the other segment buttons should change back to normal state (i.e. slide up, and change back to a non-highlighted background image). The standard UISegmentControl does this automatically for us. So how can we simulate that behavior?

It turns out to be pretty easy. All we need to do is to loop through all the segment buttons, and change their state accordingly:

- (void)segmentButtonIsHighlighted:(SegmentButtonView *)highlightedSegmentButton {
    for (SegmentButtonView *segmentButton in self.segmentButtons) {
        if ([segmentButton isEqual:highlightedSegmentButton]) {
            [segmentButton setHighlighted:YES animated:YES];
        } else {
            [segmentButton setHighlighted:NO animated:YES];
        }
    }
}

Please note that a good separation of concerns in design is needed to make the code clean, and it’s a general guideline not just for this little piece of demo. I’ve seen some segment controls designed in a way that the state and behaviors of segment buttons are mixed into the custom segment control itself, which make it quite difficult to separate the responsibility of each part of the system from each other. A good encapsulation in this case will always go a long way.

picture

Feedback

OK that’s it. If you are interested to know more about the implementation details, please check out this sample project that I created for this blog. Please note that I’ve included a few images from Jamie’s Recipes app in the sample project for demonstration purpose. The copyright of these assets are of their original authors. (Kudos to the Zolmo team for creating the app!)

As usual, if you have any questions, or if you know a different/better way, please let me know by leaving a comment below. If you like my articles, please consider subscribing to my blog or following me on Twitter.

Enjoy!

Filed under iOS iPhone ObjectiveC

2 notes &

How does the Jamie’s Recipes app strike out shopping list items with animation?

I was in the process of writing proposals to a few prospects last week. One of the projects is about developing a recipe app. This sounds like an exciting project, although I haven’t used any recipe app before, because my cooking style is a bit different. (I’m a lacto-ovo-vegetarian, with a tendency towards raw food whenever I can)

So I downloaded a few recipe apps from App Store and started to play with them, just to give myself some ideas around what makes a recipe app, and how a good recipe app can differ itself from a bad one.

I am immediately fascinated by a beautiful app called Jamie’s Recipes. It’s created by a design firm called Zolmo (http://zolmo.com). I put their link here because I really want to show my respect to them.

I’m amazed by the level of details that they put in. It’s such a carefully crafted piece of art that I just can’t stop saying wow again and again like a kid as I use it. My geek nerve tells me that this is an app that I should learn something from it. So I start to analyze the app from a software engineer’s perspective, and try to figure out how some of the best parts were possibly made.

My analysis goes pretty smoothly as I compile their code in my head, until a seemingly small feature shows up: There is a shopping list in the app. At any time users can tap on an item in the list, and there will be a smooth animation which strikes out that item from left to right quickly, which looks very much like striking out an item from one’s notebook with a pencil. I’m amazed and puzzled by that animation. How did they do that?

picture

I start to hack around in core graphics. I’m able to “erase” an image using a combination of transparency layer (CGContextBeginTransparencyLayer) and blend mode (CGContextSetBlendMode), and I can animate my drawing by firing a timer repeatedly. But I am not comfortable with that approach. I tend to use core graphics as an approach to generate static interface, and to me it’s just awkward to do any serious animation with that.

So I turn to UIView animation for help. After a few failed attempts, I come to an assumption that as long as I can draw a portion of an UIImage (instead of a resized full image), then the problem will be solved, because chaining things together using UIView animation is easy. With that on my mind, I come up with the following solution:

  • Create a custom view that is smaller in size than the full strike image
  • Add the strike image as a subview onto that custom view
  • Set masksToBounds of that custom view’s underlying core animation layer to YES so that it clips anything that goes beyond its bounds. In our case, the strike image will be clipped
  • Animate the custom view’s frame so that it reveals the image a bit at a time, until the whole image is revealed

Here is a demonstration of what that means in code (I’m using ARC here):

// In a custom view - This can be a custom
// UITableViewCell used in the shopping list
- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Creates a label to displays the name of an item in the shopping list
        [self addSubview:[[UILabel alloc] initWithFrame:CGRectMake(0, 10, frame.size.width, frame.size.height - 20)]];

        // Creates a container view which is smaller in size to hold the image
        self.containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 20, 0, frame.size.height)];
        self.containerView.layer.masksToBounds = YES;
        [self addSubview:self.containerView];

        // Adds the image as a child view to the above container view
        [self.containerView addSubview:[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"strike_out.png"]]];
    }
    return self;
}

This is what happens when user taps on or swipes the cell:

- (void)strike {
        [UIView animateWithDuration:0.4 animations:^{
            self.containerView.frame = CGRectMake(0, 20, self.bounds.size.width, self.bounds.size.height);
        } ];
}

This is what it looks like if I run the above code:

picture

Now I can strike as many items as I want in my shopping list. ;)

OK. So this is how I analyzed and solved the problem. I think there certainly will be other ways to do it. If you know of anything that is different or better, please leave a comment below so that we all learn from each other. If you are interested in my articles, please consider following me on Twitter.

Filed under ObjectiveC iOS iPhone

4 notes &

DYNavigationController - A simple swipe based library for creating chromeless iOS apps

Up until about a year ago, iOS developers could still create their killer apps happily by hooking up a few UITableViews with a standard UINavigationController and/or UITabBarController, and put their favorite stuff in.

But things have changed. Nowadays, iOS developers and designers are making every effort to tweak the standard iOS controls (oh the mighty iOS HIG!) to produce user experiences that are more natural and visually appealing. Refresh buttons are now replaced by a pull-to-release gesture, tappable areas are becoming smaller (which is not always great), alerts are not getting in the way, unnecessary navigation controls are removed altogether for a chromeless experience…

Yes, it’s this chromeless thing that keeps me excited. As I mentioned in a previous post, I love the way how the author of Logos Quiz designed its navigation that only very few navigation controls (a button to navigate to a different view, and a custom back button to navigate back) are left on screen. There is also an app called Backboard, which has taken chromeless to an extreme by dropping out on-screen navigation controls completely. There is no navigation bar, there is no tab bar. The entire screen is reserved to the app, and one just needs to swipe here and there to navigate around. What a simple yet natural experience!

picture

As I said previously, I’m such a curious person that when I see something beautiful I really want to figure out how it’s made (no, I don’t mean Mona Lisa), and in this context it’s the gesture based navigation that draws my attention. So I sit down and hacked on my Mac for a few hours, and then I came up with my solution: DYNavigationController.

DYNavigationController

With DYNavigationController, you can easily create chromeless iOS apps that are purely or partially gesture based. There are just a few steps to follow if you want to use it in your own project.

  • Step 1: Download the source code from github and copy two files (DYNavigationController.h and DYNavigationController.m) to your project.

  • Step 2: In your appDelegate (or wherever that fits your situation), import DYNavigationController.h, create an instance of DYNavigationController, and give it a rootViewController, which is your main view controller (I’m NOT using ARC here in this example):

    -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    
        // Override point for customization after application launch.
        RootViewController *rootViewController = [[[RootViewController alloc] init] autorelease];
        DYNavigationController *navigationController = [[[DYNavigationController alloc] initWithRootViewController:rootViewController] autorelease];
    
        self.window.rootViewController = navigationController;
        [self.window makeKeyAndVisible];
        return YES;
    }
    
  • Step 3: In your root controller, there are two ways that you can do to push a new view controller onto your current view stack. (This is similar to pushViewController: animated: in UIViewController). First of all, you need to implement a protocol called DYNavigationControllerDelegate in your root controller’s header:

    @interface RootViewController : UIViewController <DYNavigationControllerDelegate>
    

    And then, if you want your user to navigate to a new view by tapping on a button or a table view cell, you can implement an optional property navigator in your root controller; or if you just want your user to swipe from right to left on the screen to go to the next view, you can simply override an optional method called viewControllerToPush. I’ll explain the two approaches here one by one. You can choose whether you want to go with step 3A or 3B.

    • Step 3A: Tap to navigate. Implement the navigator property like this:

      @implementation RootViewController
      
      @synthesize navigator = _navigator;
      

      and then in the place where you want to push a new view controller (e.g. in a button handler) call pushViewController on navigator:

      - (void)nextButtonTapped {
          DetailViewController *detailViewController = [[[DetailViewController alloc] init] autorelease];
          [self.navigator pushViewController:detailViewController];
      }
      
    • Step 3B: Swipe to navigate. Override the following method in your root controller:

      - (UIViewController *)viewControllerToPush {
          return  [[[DetailViewController alloc] init] autorelease];
      }
      

      This tells DYNavigationController which view controller to push to the current view stack, and DYNavigationController will take care of the rest.

    Your user can navigate back anytime by swiping from left to right.

Please note that, if you don’t want your user to navigate to a new view from root controller or any view controller in your app, you don’t need to do any of the things mentioned in step3. DYNavigationController automatically takes care of everything that is necessary to display your view and navigate back.

Final Thoughts

There you have it. I hope this makes it easy for you to write your next chromeless iOS app. I’ll definitely consider designing my next iOS app to be as chromeless as possible (if that makes sense).

As always, if you have any questions, please don’t hesitate to contact me by leaving a comment below or following me on Twitter. If you use DYNavigationController in any of your projects, I’d love to hear. :)

Enjoy!

Update [2012-5-29]

I’m very pleased to find out that DYNavigationController is selected as Control of the Week by CocoaControls.com. You can find out more about it here. They’ve even made a short video showing DYNavigationController in action! Many thanks to the Cocoa Controls folks! :)

Filed under ObjectiveC iOS iPhone OSS

0 notes &

How to make a Logos Quiz style popup view

I love beautiful apps. Whenever I see a beautiful app, I will not only try it, but also spend some time trying to understand how some of the features in the app are made.

Logos Quiz is such an app. It’s made by Javier Perez Estarriaga. It keeps a very good balance between putting enough features in and keeping the app minimal in general. There are quite a few places in the app (such as the beautiful navigation design) that I really want to figure out, and today I am going to start with one of them - the beautiful popup view.

picture

Analysis

There are a few elements in the popup:

  1. A background view with rounded corners and shadow. I guess I can re-create that using either Core Graphics drawing or Core Animation. Core Animation might be easier here especially when performance is not a concern on this view.

  2. A title area with half of it reaches beyond the border of the background view. This should be straight forward. A transparent UILabel should do it.

  3. A close button that also reaches beyond the border of the background view. This is similar to the title.

Implementation

The background view is a good place to start. The following method creates a new CALayer, makes a few changes to it, and adds it as a sublayer to the current layer hierarchy.

- (void)setUpBackground {
    CALayer *layer = [CALayer layer];
    layer.frame = self.bounds;
    layer.masksToBounds = NO;
    layer.cornerRadius = 8;
    layer.shadowColor = [UIColor darkGrayColor].CGColor;
    layer.shadowOffset = CGSizeMake(0, 6);
    layer.shadowOpacity = 1.0;
    layer.shadowRadius = 8.0;
    [self.layer addSublayer:layer];
}

You can see that I’m using a technique that is very similar to the one described in this article. However, there is one major difference in the above code that makes the shadow looks really nice in Logos Quiz’s design, which is a call to shadowRadius. The result looks totally different if I remove that call. You can try that out if you are interested. :)

You might wonder why I set up the above settings on a new CALayer instead of self.layer. The reason is, if I set shadow on self.layer, then that shadow will appear underneath the button as well. It may or may not be what you need depending on your situation. Here if we check the original design of Logos Quiz, there is no shadow underneath the button.

Now that we are done with the background, it’s time to move on to the title. As I said above, You can create that title by either making direct Core Graphics calls, or by putting a transparent UILabel there and set the desired color, font, etc. In my case, I used the UILabel approach with the font name set to STHeitiSC-Light. This font is by far the best match among those that ship with iOS. You can check out a list of available fonts from here. You can also choose whatever font that fits your design as well.

Since this is a very easy job, I’m not going to post my code here.

The last part is to create the button. This is no different from the title - you need to set the correct location (as also described in this article), and use an image that you like. The following is what I did in order to create, configure and show the button:

- (void)setUpCloseButton {
    UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
    UIImage *image = [UIImage imageNamed:@"close_view.png"];
    [closeButton setBackgroundImage:image forState:UIControlStateNormal];
    closeButton.frame = CGRectMake(self.bounds.size.width - image.size.width / 2, self.bounds.size.height - image.size.height / 2, image.size.width, image.size.height);
    [closeButton addTarget:self action:@selector(closeButtonTapped) forControlEvents:UIControlEventTouchUpInside];
    [self addSubview:closeButton];
}

However, if you run the above code, and try to tap an area that is inside the button but outside the background (e.g. the bottom right part of the button), the UIControlEventTouchUpInside event will not be fired. This is because during a hit test, a method named pointInside: withEvent is called on the receiving view (and its sub views) to determine if the point associated with that event is within that view. However, by default pointInside: withEvent returns NO when the point is outside of that view’s bounds. In our case, that view is the background.

In order to make it work, we just need to override pointInside: withEvent on the class that acts as the background, and check to see if the point is within the button:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint pointInButton = [self convertPoint:point toView:self.closeButton];
    if ([self.closeButton pointInside:pointInButton withEvent:event])
        return YES;
    else
        return [super pointInside:point withEvent:event];
}

Now when you tap on any part of the button, UIControlEventTouchUpInside will fire properly and our button handler will be called.

picture

So this is it. If you have any questions or if you know better ways to do it, please feel free to leave a comment below. :)

Enjoy!

Filed under iOS iPhone

1 note &

Using ARC enabled code in your non-ARC project

I’ve always been a bit late at adopting new technologies, such as this brilliant thingy called ARC from Apple. I didn’t want to touch it because I was quite confident with my Objective-C memory management knowledge. Until one day when I ran the analysis check in Xcode, I was surprised to see a long list of memory warnings coming from a 3rd party library that I was using.

It turned out that that library was using ARC, and I didn’t know it because, well, I didn’t know ARC.

So I was faced with this - either I needed to convert my whole project to using ARC, or I needed to find a non-ARC alternative to that library. I googled a lot trying to see if there are other options. There were tons of articles talking about how one can integrate non-ARC enabled code in an ARC enabled project, or how one should convert projects to using ARC, which is good advice, but I didn’t want to do that at that time since I was about to release.

And then I read this excellent article Mixing ARC and Non-ARC Code with Static Libraries written by Duane Fields on how one can mix ARC and non-ARC code together using static library. Although it still talks about the opposite way, I was inspired by Duane’s solution and decided to follow that path but turn things around.

Steps

So here is how I did it:

  1. Create a new target in the non-ARC project in Xcode. Select the “Cocoa Touch Static Library” template, and make sure “Use Automatic Reference Counting” is checked. This target will be used to hold the ARC enabled code.

    picture

    picture

  2. Now that the new target is created, you can start to add the ARC code files into this target, just as you would normally do for a normal Xcode project. Keep in mind that when Xcode asks for what files to add, there is an option named “Add to targets”. In that option the newly created target should be checked.

    picture

  3. Let’s figure out target dependencies and linker settings. Click on the name of your project in project navigator and open the project settings page. Select your main target in “Targets”, click on “Build Phases”, and expand “Target Dependencies”. Click on the “+” button and add the new static library target from there. In this way Xcode will be able to handle the relationship between the targets and build in the right order (if both targets are within the same Xcode workspace, then this is not required but recommended. Read this for a reference).

    picture

    And then while the “Build Phases” tab is still open, expand “Link Binary with Libraries”, click on the “+” button, and add your static library target from there. If there are other framework/libraries that your static library target depends on, you might want to add them as well.

    picture

    There you should have it. You can use your favorite retain/release etc in the main target, and keep the ARC “plagued” code separated in static libraries, at least for a while, until you are comfortable with ARC. ;)

A bit testing

I wasn’t sure if this approach would work at first, since there wasn’t any one talking about this approach on the internet (not that I am aware of, if there are please let me know), and therefore my best bet would be to test out myself. So I did quite some memory testing. I tested allocations and memory leaks using Instruments, and verified no instances of ARC enabled classes were leaked. I also manually added debug logs in the dealloc methods of ARC enabled classes, and verified that the class instances that were created were successfully deallocated.

Given that most of ARC (other than weak reference) is just compiler magic, as long as the correct compiler (Apple LLVM compiler 3.0 and beyond) is used, and that the minimal deployment target (iOS 4.0 and newer) is met, I assume the output will just be fine. If you run into any issues, please let me know by leaving a comment below.

Enjoy!

Filed under ObjectiveC Xcode iOS iPhone ARC

2 notes &

DYRateView - A simple yet powerful rating control for your next iOS project

When I worked on the latest update of my iOS app Veggie in China, I needed a way to enable my users to rate restaurants. I wanted to have a rating view which displays five stars, and that user can tap on any of the stars to rate.

I googled a bit and found this tutorial written by Ray Wenderlich. It worked great. However, there were a few things that I really needed missing from that tutorial:

  • I need the rating view to display decimals. For example, it needs to display not only 5 but also 4.3, 2.7, etc.
  • I need the rating view to align in more than one way. In one screen of my app, I want it to align to the right, while in another screen, I want it to align in the center.
  • I need the rating view to display in two sizes: A smaller size to show readonly data, and a bigger size for users to tap (it’s quite difficult for user to tap within a small area to rate).
  • I want user to tap any of the empty space to the left of the rating view to set it to zero, and tap any of the empty space to the right of the rating view to set it to five. This is how the rating view in Apple’s App Store app works, and I think it’s a handy feature that makes a lot of sense.

Based on these ideas, and since imitation is the greatest form of flattery (thanks Ray!), I decided to create my own one. So DYRateView was born. :)

This is how it’s used in Veggie in China:

picture

picture

Usage

It’s very simple to use DYRateView in your project. All you need to do is to download the source code from Github, and add the following two files to your project:

  • DYRateView.h
  • DYRateView.m

I’ve also made a few images myself for the stars. You are welcome to use them if you don’t want to create your own ones. Just drag them from the Resources folder into your project and make sure the files are copied over.

When you are done adding DYRateView to your project, you can start using it. The following shows an example:

DYRateView *rateView = [[[DYRateView alloc] initWithFrame:CGRectMake(x, y, 100, 14)] autorelease];
rateView.rate = 4.7;
rateView.alignment = RateViewAlignmentRight;
[self.view addSubview:rateView];

You can make it editable:

rateView.editable = YES;

and then hook it up to a delegate in order to receive updated rate whenever user makes a change:

rateView.delegate = self;
// ….

#pragma mark - DYRateViewDelegate

- (void)changedToNewRate:(NSNumber *)rate {
    self.rateLabel.text = [NSString stringWithFormat:@"Rate: %d", rate.intValue];
}

I’ve added a sample project in Github to demonstrate its usage:

picture

Feedback

That’s it. I’ll be more than happy if you find DYRateView useful. Feel free to let me know any code related issues on Github or in the comments below.

Enjoy!

Filed under OpenSource iOS iPhone objectivec OSS

1 note &

How to draw shadow underneath UINavigationBar

In my latest app Veggie in China, I have a custom UINavigationBar which displays a custom image I made in the background. In order to make the navigation bar looks more real, I wanted to add a bit shadow underneath it to give the design a bit more depth. This is also similar to what the authors of tea app did in their app.

In order to display the custom background image, I already had a subclass of UINavigationBar in my codebase. What I needed to do is just to add a few more lines in that class to display the shadow:

@implementation CustomNavigationBar

- (void)awakeFromNib {
    [super awakeFromNib];
    _image = [UIImage imageNamed:@"Title.png"];
    self.tintColor = [UIColor colorWithRed:46.0 / 255.0 green:149.0 / 255.0 blue:206.0 / 255.0 alpha:1.0];

    // draw shadow
    self.layer.masksToBounds = NO;
    self.layer.shadowOffset = CGSizeMake(0, 3);
    self.layer.shadowOpacity = 0.6;
    self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
}

- (void)drawRect:(CGRect)rect {
    [_image drawInRect:rect];
}

@end

The above code should generate something that looks like this:

picture

The key in the above code is to make sure masksToBounds is set to NO (which is the default value, I put it in the code just to make it clear). This tells the CALayer to allow any drawing that is done outside of its boundaries. This is also the key to the effect that I demonstrated in another post.

You can download a sample project that contains the above code here.

Filed under iOS objectivec iPhone

1 note &

How to create an arrow that goes beyond parent view’s frame

One of the eye-catching designs that some really good iPhone apps have is to have a design element (e.g. an arrow) that goes beyond its parent view’s frame. For example, this is what the app Foodspotting has:

picture

Please note the little arrow that “peeks” out from the header. How can we create that?

While, it turns out to be pretty easy. All we need to do is:

  • Create a UITableView
  • Create a custom UIView to represent the header
  • Apply the custom header to the tableView in tableView’s tableView: viewForHeaderInSection: method
  • In the custom header, create an UIImageView with the arrow image, add it as a subview to the custom header view, and most importantly, set the UIImageView’s frame to be just underneath the custom header view’s bounds

The following code shows what the code might look like to set the arrow up in our custom header view:

- (void)setUpArrow:(CGRect)frame {
    // Set up an imageView in order to display the arrow - The image view is placed right below the bottom so that the arrow appears 'peeking out'
    UIImageView *arrowImageView = [[UIImageView alloc] initWithImage:self.arrowImage];
    arrowImageView.frame = CGRectMake(ARROW_LEFT_X, frame.size.height, self.arrowImage.size.width, self.arrowImage.size.height);
    arrowImageView.alpha = 0.9;
    [self addSubview:arrowImageView];
    [arrowImageView release];
}

This is what it looks like:

picture

A sample project that demonstrates the above mentioned steps can be found here.

Filed under UITableView objectivec iOS iPhone

1 note &

How to add multiple UITableViews in the same view

Most of the time, UITableView is used to take up the entire screen to present content to the user, but there are times when multiple UITableViews are needed in the same view. One such example is the Foodspotting app. In its ‘Follow People’ screen, there are two UITableViews - One in the top, showing three options to find more users to follow, another below it, showing a list of following users.

picture

This can be done by creating a plain style UITableView which shows the user list, and creating another grouped style UITableView and putting it into the header of the first UITableView. Then we can create a UITableViewController, and set it to be the data source and delegate of the UITableView in the header.

- (void)viewDidLoad
{
    [super viewDidLoad];

     // Create a second UITableView and put it into the header
    UITableView *headerTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 150) style:UITableViewStyleGrouped];

    // Create a UITableViewController and set it to be the datasource and delegate of the newly created tableView
    self.headerController = [[HeaderTableViewController alloc] init];
    headerTableView.dataSource = self.headerController;
    headerTableView.delegate = self.headerController;

    self.tableView.tableHeaderView = headerTableView;
}

Please note that HeaderTableViewController is a subclass of UITableViewController. You can put whatever logic you’d like in it to control the behavior of the upper UITableView, just as what you’d do with a normal UITableView.

Please also note that the same effect can also be done by using one UITableView. One needs to put the upper and lower parts into different sections, and adjust the visual styles accordingly.

However, this approach requires more work, especially if the two UITableViews have different table style (grouped vs plain). Since one UITableView can only have one style, one needs to either create custom UITableViewCells to make plain cells look like grouped cells, or make some changes in the setFrame: method to make grouped style cells look like plain ones. Also, by putting controller logic into one controller instead of two, the code will not be as clean as the one shown above.

Filed under UITableView iOS iPhone objectivec