Annotations in a map on iOS is pretty straightforward. And dynamically loading an image into UIImageView can be achieved by many good extensions like SDWebImage . But how do you combine these two and display images asynchronously in an annotation? The answer is to customize MKAnnotationView.

Custom MKAnnotationView

You can extend MKAnnotationView in conjunction with SDWebImage. Here are the key parts of the custom class MKAnnotationView+WebCache.m:

#import "MKAnnotationView+WebCache.h"
#import "SDWebImageManager.h"
#import "SDImageCache.h"

@implementation MKAnnotationView(WebCache)

- (void)setImageWithURL:(NSURL *)url
{
    [self setImageWithURL:url placeholderImage:nil];
}

- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder
{
    SDWebImageManager *manager = [SDWebImageManager sharedManager];
    [manager cancelForDelegate:self];
    [self setImage:placeholder];
    if (url)
    {
        [manager downloadWithURL:url delegate:self];
    }
}

- (void)cancelCurrentImageLoad
{
    [[SDWebImageManager sharedManager] cancelForDelegate:self];
}

- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image
{
    [self setImage:image];
}

@end

 

Here is how you can use the above custom class in your project:

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation
{
    CustomAnnotation *anno = (CustomAnnotation *)annotation;

    static NSString *defaultPinID = @"customIdentifier";

    MKAnnotationView *pinView = (MKAnnotationView *)[self.mapview dequeueReusableAnnotationViewWithIdentifier:defaultPinID];

    if (pinView == nil)
        pinView = [[[MKAnnotationView alloc] initWithAnnotation:anno reuseIdentifier:defaultPinID] autorelease];
    pinView.opaque = NO;
    pinView.canShowCallout = NO;
    pinView.draggable = NO;
    pinView.annotation = anno;

    NSURL *ImageURL = [NSURL URLWithString:IMAGE_URL_STRING];
    [pinView setImageWithURL:ImageURL placeholderImage:[UIImage imageNamed:@"loading.png"]];

    return pinView;
}



You can download the complete source from github here

PS::You can  get updates of our work @mokriya and more sample projects from our github page

The default behavior of the Tab Bar in iOS is to show a blue gradient on the selected Tab image. This does not always go well with the rest of the UI design for your app. However, you can easily customize the Tab Bar.

Buttons in the Tab Bar

You can add UIButtons in the Tab Bar and provide different images for the Normal and Selected state for the buttons.

Here are the key methods in the custom class MokriyaUITabBarController that extends UITabBarController:

- (void)customizeTabBar
{
    [self customizeTabBarImages];
    [self customizeTabBarLabels];
    [self didSelectTab:[tabBarButtonsArray objectAtIndex:0]];

}

- (void)customizeTabBarImages
{
    tabBarButtonsArray = [[NSMutableArray alloc] initWithCapacity:0];
    int tabItemsCount = [self.viewControllers count];

    if (tabItemsCount!= [tabBarImagesArray count] || tabItemsCount != [tabBarSelectedStateImagesArray count]||tabItemsCount != [tabBarTitlesArray count]) {
        NSLog(@"tabItemsCount!= [tabBarImagesArray count] || tabItemsCount != [tabBarSelectedStateImagesArray count] ||tabItemsCount != [tabBarTitlesArray count] ");
        NSLog(@"Fix it");
        return;
    }

    float tabWidth = self.tabBar.frame.size.width/tabItemsCount;
    float tabHeight = self.tabBar.frame.size.height;
    float tabXCenter=tabWidth/2;
    float tabYCenter = self.tabBar.frame.size.height/2;
    for (int i=0;i < [self.viewControllers count]; i++) {
        UIButton *tabBarButton = [UIButton buttonWithType:UIButtonTypeCustom];
        tabBarButton.tag = i;
        [tabBarButton addTarget:self action:@selector(didSelectTab:) forControlEvents:UIControlEventTouchDown];
        UIImage *buttonImage = [tabBarImagesArray objectAtIndex:i];
        tabBarButton.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin;
        tabBarButton.frame = CGRectMake(0.0, 0.0, tabWidth,tabHeight);

        [tabBarButton setImage:buttonImage forState:UIControlStateNormal];
        [tabBarButton setImage:buttonImage forState:UIControlStateSelected];
        [tabBarButton setImage:buttonImage forState:UIControlStateHighlighted];
        [tabBarButton setImage:buttonImage forState:UIControlStateSelected|UIControlStateHighlighted];
        tabBarButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
        tabBarButton.imageView.image = buttonImage;
        tabBarButton.selected = YES;
        tabBarButton.center = CGPointMake(tabXCenter, tabYCenter);
        tabXCenter += tabWidth;
        [tabBarButtonsArray addObject:tabBarButton];
        [self.tabBar addSubview:tabBarButton];

    }

}

- (void)customizeTabBarLabels
{

    tabBarLabelsArray = [[NSMutableArray alloc] initWithCapacity:0];

    int tabItemsCount = [self.viewControllers count];

    if (tabItemsCount!= [tabBarImagesArray count] || tabItemsCount != [tabBarSelectedStateImagesArray count]) {
        NSLog(@"tabItemsCount!= [tabBarImagesArray count] || tabItemsCount != [tabBarSelectedStateImagesArray count]");

        return;
    }

    float tabWidth = self.tabBar.frame.size.width/tabItemsCount;

    float tabXCenter = tabWidth/2;
    float tabYCenter = self.tabBar.frame.size.height-7;

    for (int i=0;i < [self.viewControllers count]; i++) {
        UILabel *tabBarLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0,tabWidth, 50)];
        tabBarLabel.text = [tabBarTitlesArray objectAtIndex:i];
        tabBarLabel.center = CGPointMake(tabXCenter, tabYCenter);
        tabBarLabel.backgroundColor = [UIColor clearColor];
        tabBarLabel.font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:10];
        tabBarLabel.textAlignment = UITextAlignmentCenter;
        tabXCenter += tabWidth;
        [self.tabBar addSubview:tabBarLabel];
        [tabBarLabelsArray addObject:tabBarLabel];
        [tabBarLabel release];
    }

}

- (void)didSelectTab:(id)sender
{
    UIButton *selectedButton = (UIButton *)sender;
    self.selectedIndex = selectedButton.tag;

    for (int i=0; i<[tabBarButtonsArray count]; i++) {
        if (selectedButton.tag==i) {
            UILabel *tabBarLabel = [tabBarLabelsArray objectAtIndex:i];
            tabBarLabel.textColor = [UIColor whiteColor];

            continue;
        }
        UIImage *buttonImage = [tabBarImagesArray objectAtIndex:i];
        UIButton *button = [tabBarButtonsArray objectAtIndex:i];
        [button setImage:buttonImage forState:UIControlStateNormal];
        [button setImage:buttonImage forState:UIControlStateSelected];
        [button setImage:buttonImage forState:UIControlStateHighlighted];
        [button setImage:buttonImage forState:UIControlStateSelected|UIControlStateHighlighted];

        UILabel *tabBarLabel = [tabBarLabelsArray objectAtIndex:i];
        tabBarLabel.textColor = [UIColor blackColor];

    }

    UIImage *buttonImage = [tabBarSelectedStateImagesArray objectAtIndex:selectedButton.tag];
    [selectedButton setImage:buttonImage forState:UIControlStateNormal];
    [selectedButton setImage:buttonImage forState:UIControlStateSelected];
    [selectedButton setImage:buttonImage forState:UIControlStateHighlighted];
    [selectedButton setImage:buttonImage forState:UIControlStateSelected|UIControlStateHighlighted];
}



And here is how you can use it in your project:

#import "MokriyaUITabBarController.h"

tabBarController = [[MokriyaUITabBarController alloc] init];
    FirstViewController *firstView = [[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil];
    SecondViewController *secondView = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
    ThirdViewController *thirdView = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil];
    FourthViewController *fourthView = [[FourthViewController alloc] initWithNibName:@"FourthViewController" bundle:nil];
    FifthViewController *fifthView = [[FifthViewController alloc] initWithNibName:@"FifthViewController" bundle:nil];

    tabBarController.viewControllers = [NSArray arrayWithObjects:firstView,secondView,thirdView,fourthView,fifthView,nil];

    tabBarController.tabBarImagesArray = [NSMutableArray arrayWithObjects:
                                          [UIImage imageNamed:@"m1.png"],
                                          [UIImage imageNamed:@"m2.png"],
                                          [UIImage imageNamed:@"m3.png"],
                                          [UIImage imageNamed:@"m4.png"],
                                          [UIImage imageNamed:@"m5.png"],
                                          nil];

    tabBarController.tabBarSelectedStateImagesArray = [NSMutableArray arrayWithObjects:
                                          [UIImage imageNamed:@"am1.png"],
                                          [UIImage imageNamed:@"am2.png"],
                                          [UIImage imageNamed:@"am3.png"],
                                          [UIImage imageNamed:@"am4.png"],
                                          [UIImage imageNamed:@"am5.png"],
                                          nil];
    tabBarController.tabBarTitlesArray = [NSMutableArray arrayWithObjects:
                                          @"Home",
                                          @"About Us",
                                          @"Services",
                                          @"Portfolio",
                                          @"Contact Us",
                                          nil];

    [firstView release];
    [secondView release];
    [thirdView release];
    [fourthView release];
    [fifthView release];
    [tabBarController customizeTabBar];

    self.window.rootViewController = self.tabBarController;



You can also tweak MokriyaUITabBarController.m to get desired Height/Width of TabBarItem

You can download the complete source code from our github here

PS::You can  get updates of our work @mokriya and more sample projects from our github page

We have done a lot of custom features in Blackberry, but I will be the first ones to agree that it is not the easiest platform to develop apps for. However, if you have the right approach, most of the features that can be done in iOS or Android can also be implemented in Blackberry.

We were fortunate to work on Path’s Blackberry application. The animation to drop-open the picture is probably one of the best UI features that differentiate Path from a lot of other photo-sharing apps. And I think we nailed it better than the current Android version of Path.



Here is a quick overview of the key steps to customize a Field to get this animation effect. The base class of this is the custom BitmapButtonField class provided by RIM.

Create a class that extends BitmapButtonField and use the code below along with any other customizations you need.

   public void expandToggle() {
      showExpanded = !showExpanded;      
      setFocus();
      relayoutParent();
   }
   
   public void relayoutParent() {
      UiApplication.getUiApplication().invokeLater(new Runnable() {
         public void run() {
            try {
               // In this case the Manager of this field happens to be a TwoColumnField (See above Blackberry library link)
               final TwoColumnField manager = (TwoColumnField) getManager();
               manager.relayout();
               Thread.sleep(EXPAND_TIMER); // The time value can be customized per your need
            } catch (Exception e) {
            }
         }
      });
   }
	
   protected void layout( int width, int height ) {
      setExtent( getPreferredWidth(), getPreferredHeight() );
      if (showExpanded && expandFactor < FIELD_MAX_HEIGHT          //expand
            || !showExpanded && expandFactor > FIELD_MIN_HEIGHT) { //shrink

         relayoutParent();
      }
    }
    
   public int getPreferredHeight() {
      // Use your own custom values for the below:
      // expandIncrement - how much the Field should expand for each animation
      // FIELD_MAX_HEIGHT - The maximum height of the Field to which it needs to expand
      // FIELD_MIN_HEIGHT - The minimum height of the Field to which it needs to shrink

      if (showExpanded) {
    	  expandFactor += expandIncrement;
    	  if (expandFactor > FIELD_MAX_HEIGHT) {
    	     expandFactor = FIELD_MAX_HEIGHT;
    	  }
      } else {
    	  expandFactor -= expandIncrement;
    	  if (expandFactor < FIELD_MIN_HEIGHT) {
    		  expandFactor = FIELD_MIN_HEIGHT;
    	  }
      }
      return expandFactor;         
   }


In TwoColumnField class add the below method:

   public void relayout() {
      updateLayout();
   }



Thats it!

PS::You can follow me on twitter @pranilkanderi or get updates of our work @mokriya

A Developer conference that was not from Apple or Google and it still had a huge turnout. I think thats a clear indication of the potential and the growth of Android as the leading mobile platform. AnDevCon was full of good information and a very excited group of developers.

I met some indie developers, but it was interesting to meet developers from enterprise companies that are realizing the value of a mobile platform like Android and currently seem to enter the space, albeit at their own pace. And for indie developers, one of the biggest gripe about Android is the monetization strategy when compared to iOS platform, which seemed to be partially solved with the number of products that were available for billing and most of them offered carrier billing as well. About 50% of the exhibits were about payment services, including the big player PayPal X, who is currently offering a $25K developer challenge and free marketing promotion for the winner. Great things and a lot of excitement. No wonder why they have already announced AnDevCon 2 in November.

As promised in my previous blog about AnDevCon below are the presentations and the source code for both the classes. I had a blast presenting at the conference (it was good to showcase Mokriya as well ;) - see the below slide of a tweet) and I am certainly looking forward for the next one:



All Presentations can be accessed from here: http://andevcon.com/speakerpresentations.html

Building Location-Based Applications in Android - Part 1:

Building Location-Based Applications in Android - Part 2:



PS::You can follow me on twitter @pranilkanderi or get updates of our work @mokriya

The default tabs on an Android device do not look good and they are not easily customizable. You can provide a Drawable or a View as an indicator, and that would provide some customization. But for the tabs to look something like the below screen, it would not be possible to use either option.



One of the best solutions is to use Radio buttons. Radio buttons are a great solution since they can behave like tabs, where only one item is active at a time. Since they are buttons they can be customized as you want and can be placed where you would like. The Radio Buttons can be placed horizontally and below the RadioGroup a FrameLayout with all the other tabs included and visibility set to gone will do the trick.

Follow the detailed steps below to create your own version of custom tabs.

Here is a snippet of code to create a Radio Group that can be in the onCreate method of your activity:

protected void onCreate(Bundle savedInstanceState) {

              //Set your content view and initialize other views

              // Initialize the Views that would normally go as a
	      tabView1 = findViewById(R.id.Tab1);
	      tabView2 = findViewById(R.id.Tab2);
	      tabView3 = findViewById(R.id.Tab3);
	      tabView4 = findViewById(R.id.Tab4);

	      tabView1.setVisibility(View.VISIBLE);
	      tabView2.setVisibility(View.GONE);
	      tabView3.setVisibility(View.GONE);
	      tabView4.setVisibility(View.GONE);

	      tabs = (RadioGroup) findViewById(R.id.AllTabs);

	      tabs.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {

	         public void onCheckedChanged(RadioGroup group, int checkedId) {

	            tabView1.setVisibility(View.GONE);
	            tabView2.setVisibility(View.GONE);
	            tabView3.setVisibility(View.GONE);
	            tabView4.setVisibility(View.GONE);

	            switch (checkedId) {
	               case R.id.RadioButton1:
	                  tabView1.setVisibility(View.VISIBLE);
	                  break;
	               case R.id.RadioButton2:
	                  tabView2.setVisibility(View.VISIBLE);
	                  break;
	               case R.id.RadioButton3:
	                  tabView3.setVisibility(View.VISIBLE);
	                  break;
	               case R.id.RadioButton4:
	                  tabView4.setVisibility(View.VISIBLE);
	                  break;

	            }
	         }
	      });

              //Other code
}


The layout of your tabs screen should look like this:

<?xml version="1.0" encoding="utf-8"?&rt;
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical" android:background="@drawable/app_bg"
	android:layout_width="fill_parent" android:layout_height="fill_parent"&rt;
	
	<RelativeLayout android:layout_width="fill_parent"
		android:layout_height="wrap_content"&rt;

		<RadioGroup android:id="@+id/AllTabs" android:layout_width="fill_parent"
			android:layout_height="wrap_content" android:orientation="horizontal"
			android:checkedButton="@+id/RadioButton1"&rt;
			<RadioButton android:id="@+id/RadioButton1"
				android:background="@drawable/btnBg1" style="@style/TabButton" /&rt;
			<RadioButton android:id="@+id/RadioButton2"
				android:background="@drawable/btnBg2" style="@style/TabButton" /&rt;
			<RadioButton android:id="@+id/RadioButton3"
				android:background="@drawable/btnBg3" style="@style/TabButton" /&rt;
			<RadioButton android:id="@+id/RadioButton4"
				android:background="@drawable/btnBg4" style="@style/TabButton" /&rt;
		</RadioGroup&rt;
	</RelativeLayout&rt;


	<FrameLayout android:id="@+id/ViewsLayout"
		android:layout_width="fill_parent" android:layout_height="fill_parent"&rt;
		<include android:id="@+id/Tab1" layout="@layout/content_of_tab1"
			android:layout_width="fill_parent" android:layout_height="fill_parent" /&rt;
		<include android:id="@+id/Tab2" layout="@layout/content_of_tab2"
			android:layout_width="fill_parent" android:layout_height="fill_parent"
			android:visibility="gone" /&rt;
		<include android:id="@+id/Tab3" layout="@layout/content_of_tab3"
			android:layout_width="fill_parent" android:layout_height="fill_parent"
			android:visibility="gone" /&rt;
		<include android:id="@+id/Tab4" layout="@layout/content_of_tab4"
			android:layout_width="fill_parent" android:layout_height="fill_parent"
			android:visibility="gone" /&rt;

	</FrameLayout&rt;

</LinearLayout&rt;

Note that all the views that have been included in the FrameLayout have been set a visibility of ‘gone’, except for the first view that would act as the default tab.

And finally here is the style that was used for all of the RadioButton’s, which should be in your res/styles.xml:

	<style name="TabButton"&rt;
		<item name="@android:button">@null
		<item name="@android:layout_width"&rt;wrap_content</item&rt;
		<item name="@android:layout_height"&rt;wrap_content</item&rt;
		<item name="@android:layout_margin"&rt;3dp</item&rt;
	</style>


Hopefully that will help with better customization of tabs in Android.

PS::You can follow me on twitter @pranilkanderi or get updates of our work @mokriya