Wednesday, March 9, 2011

Building Custom Map Annotation Callouts – Part 1

The iPhone’s Map Annotation Callouts are very useful for displaying small amounts of information when a map pin (annotation) is selected. One problem with the standard callouts present in iOS is the inability to change the height of the callout.

For example, you may want to display a logo or other image that is taller than the default callout. Or you may want to display an address and phone number on separate lines under the title. Both of these scenarios are impossible using the standard iOS callouts. There are many steps to building a good replacement callout with the proper look and behavior, but it can be done.



Part 1 (explained here) will explain how to build a custom map callout.

Part 2 covers adding a button to the custom callout, which is not as simple as it sounds.


Put it on the map (and take it off)
For this example we will create two simple map annotations in the view controller – one will display the standard callout and the other will display the custom callout.

To place the “custom callout annotation” on the map we will add the custom annotation when the mapView calls the mapView:didSelectAnnotationView: method, and we will remove the callout on the corresponding deselect method, mapView:didDeselectAnnotationView:. In mapView:viewForAnnotation: we return an instance of our custom MKAnnotationView subclass. Also, we disable the standard callout on the “parent” annotation view, which we will show the custom callout for.

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
if (view.annotation == self.customAnnotation) {
if (self.calloutAnnotation == nil) {
self.calloutAnnotation = [[CalloutMapAnnotation alloc]
initWithLatitude:view.annotation.coordinate.latitude
andLongitude:view.annotation.coordinate.longitude];
} else {
self.calloutAnnotation.latitude = view.annotation.coordinate.latitude;
self.calloutAnnotation.longitude = view.annotation.coordinate.longitude;
}
[self.mapView addAnnotation:self.calloutAnnotation];
self.selectedAnnotationView = view;
}
}

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view {
if (self.calloutAnnotation && view.annotation == self.customAnnotation) {
[self.mapView removeAnnotation: self.calloutAnnotation];
}
}

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation {
if (annotation == self.calloutAnnotation) {
CalloutMapAnnotationView *calloutMapAnnotationView = (CalloutMapAnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:@"CalloutAnnotation"];
if (!calloutMapAnnotationView) {
calloutMapAnnotationView = [[[CalloutMapAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:@"CalloutAnnotation"] autorelease];
}
calloutMapAnnotationView.parentAnnotationView = self.selectedAnnotationView;
calloutMapAnnotationView.mapView = self.mapView;
return calloutMapAnnotationView;
} else if (annotation == self.customAnnotation) {
MKPinAnnotationView *annotationView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:@"CustomAnnotation"] autorelease];
annotationView.canShowCallout = NO;
annotationView.pinColor = MKPinAnnotationColorGreen;
return annotationView;
} else if (annotation == self.normalAnnotation) {
MKPinAnnotationView *annotationView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:@"NormalAnnotation"] autorelease];
annotationView.canShowCallout = YES;
annotationView.pinColor = MKPinAnnotationColorPurple;
return annotationView;
}

return nil;
}
Note: If building for iOS 3.x you will need to determine annotation selection another way (KVO, notifications, etc.).



Draw the callout (in the right place)
Now that we have the callout annotation placed on the map at the same coordinate as the parent annotation, we need to adjust the width and height of the callout view and adjust the center offset so that the view spans the entire width of the map and sits above the parent annotation. These calculations will be done during setAnnotation: because our contentHeight, offsetFromParent, and mapView properties should have been set by then. setNeedsDisplay will also be called in setAnnotation: so that the callout is redrawn to match up with the annotation.

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- (void)setAnnotation:(id )annotation {
[super setAnnotation:annotation];
[self prepareFrameSize];
[self prepareOffset];
[self setNeedsDisplay];
}

- (void)prepareFrameSize {
CGRect frame = self.frame;
CGFloat height = self.contentHeight +
CalloutMapAnnotationViewContentHeightBuffer +
CalloutMapAnnotationViewBottomShadowBufferSize -
self.offsetFromParent.y;

frame.size = CGSizeMake(self.mapView.frame.size.width, height);
self.frame = frame;
}

- (void)prepareOffset {
CGPoint parentOrigin = [self.mapView
convertPoint:self.parentAnnotationView.frame.origin
fromView:self.parentAnnotationView.superview];

CGFloat xOffset = (self.mapView.frame.size.width / 2) -
(parentOrigin.x + self.offsetFromParent.x);

//Add half our height plus half of the height of the annotation we are tied to so that our bottom lines up to its top
//Then take into account its offset and the extra space needed for our drop shadow
CGFloat yOffset = -(self.frame.size.height / 2 +
self.parentAnnotationView.frame.size.height / 2) +
self.offsetFromParent.y +
CalloutMapAnnotationViewBottomShadowBufferSize;

self.centerOffset = CGPointMake(xOffset, yOffset);
}


The shape of the callout bubble is basically a round-rectangle with a triangle that points to the parent annotation. Determining where that point should be is a matter of finding the x-coordinate of the parent relative to it and adding the offsetFromParent.x property. Luckily UIView contains the handy convertPoint:fromView: method to handle the conversion between coordinate systems.

The steps to draw something similar to the standard callout are as follows:

Create the shape (path) of the callout bubble with the point in the right position to match up with the parent
Fill the path and add the shadow (adding the shadow here and then restoring the context prevents the shadow from being redrawn with each subsequent step)
Apply a stroke to the path (more opaque than the fill)
Create a round rectangle path to appear as the “gloss”
Fill the gloss path with a gradient
Convert the glass path to a “stroked path” (this will allow us to apply a gradient to the stroke)
Apply a gradient (light to transparent) to the stroked path
In code:

?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
- (void)drawRect:(CGRect)rect {
CGFloat stroke = 1.0;
CGFloat radius = 7.0;
CGMutablePathRef path = CGPathCreateMutable();
UIColor *color;
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat parentX = [self relativeParentXPosition];

//Determine Size
rect = self.bounds;
rect.size.width -= stroke + 14;
rect.size.height -= stroke + CalloutMapAnnotationViewHeightAboveParent - self.offsetFromParent.y + CalloutMapAnnotationViewBottomShadowBufferSize;
rect.origin.x += stroke / 2.0 + 7;
rect.origin.y += stroke / 2.0;

//Create Path For Callout Bubble
CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y + radius);
CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height - radius);
CGPathAddArc(path, NULL, rect.origin.x + radius, rect.origin.y + rect.size.height - radius,
radius, M_PI, M_PI / 2, 1);
CGPathAddLineToPoint(path, NULL, parentX - 15,
rect.origin.y + rect.size.height);
CGPathAddLineToPoint(path, NULL, parentX,
rect.origin.y + rect.size.height + 15);
CGPathAddLineToPoint(path, NULL, parentX + 15,
rect.origin.y + rect.size.height);
CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width - radius,
rect.origin.y + rect.size.height);
CGPathAddArc(path, NULL, rect.origin.x + rect.size.width - radius,
rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);
CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y + radius);
CGPathAddArc(path, NULL, rect.origin.x + rect.size.width - radius, rect.origin.y + radius,
radius, 0.0f, -M_PI / 2, 1);
CGPathAddLineToPoint(path, NULL, rect.origin.x + radius, rect.origin.y);
CGPathAddArc(path, NULL, rect.origin.x + radius, rect.origin.y + radius, radius,
-M_PI / 2, M_PI, 1);
CGPathCloseSubpath(path);

//Fill Callout Bubble & Add Shadow
color = [[UIColor blackColor] colorWithAlphaComponent:.6];
[color setFill];
CGContextAddPath(context, path);
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake (0, self.yShadowOffset), 6, [UIColor colorWithWhite:0 alpha:.5].CGColor);
CGContextFillPath(context);
CGContextRestoreGState(context);

//Stroke Callout Bubble
color = [[UIColor darkGrayColor] colorWithAlphaComponent:.9];
[color setStroke];
CGContextSetLineWidth(context, stroke);
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextAddPath(context, path);
CGContextStrokePath(context);

//Determine Size for Gloss
CGRect glossRect = self.bounds;
glossRect.size.width = rect.size.width - stroke;
glossRect.size.height = (rect.size.height - stroke) / 2;
glossRect.origin.x = rect.origin.x + stroke / 2;
glossRect.origin.y += rect.origin.y + stroke / 2;

CGFloat glossTopRadius = radius - stroke / 2;
CGFloat glossBottomRadius = radius / 1.5;

//Create Path For Gloss
CGMutablePathRef glossPath = CGPathCreateMutable();
CGPathMoveToPoint(glossPath, NULL, glossRect.origin.x, glossRect.origin.y + glossTopRadius);
CGPathAddLineToPoint(glossPath, NULL, glossRect.origin.x, glossRect.origin.y + glossRect.size.height - glossBottomRadius);
CGPathAddArc(glossPath, NULL, glossRect.origin.x + glossBottomRadius, glossRect.origin.y + glossRect.size.height - glossBottomRadius,
glossBottomRadius, M_PI, M_PI / 2, 1);
CGPathAddLineToPoint(glossPath, NULL, glossRect.origin.x + glossRect.size.width - glossBottomRadius,
glossRect.origin.y + glossRect.size.height);
CGPathAddArc(glossPath, NULL, glossRect.origin.x + glossRect.size.width - glossBottomRadius,
glossRect.origin.y + glossRect.size.height - glossBottomRadius, glossBottomRadius, M_PI / 2, 0.0f, 1);
CGPathAddLineToPoint(glossPath, NULL, glossRect.origin.x + glossRect.size.width, glossRect.origin.y + glossTopRadius);
CGPathAddArc(glossPath, NULL, glossRect.origin.x + glossRect.size.width - glossTopRadius, glossRect.origin.y + glossTopRadius,
glossTopRadius, 0.0f, -M_PI / 2, 1);
CGPathAddLineToPoint(glossPath, NULL, glossRect.origin.x + glossTopRadius, glossRect.origin.y);
CGPathAddArc(glossPath, NULL, glossRect.origin.x + glossTopRadius, glossRect.origin.y + glossTopRadius, glossTopRadius,
-M_PI / 2, M_PI, 1);
CGPathCloseSubpath(glossPath);

//Fill Gloss Path
CGContextAddPath(context, glossPath);
CGContextClip(context);
CGFloat colors[] =
{
1, 1, 1, .3,
1, 1, 1, .1,
};
CGFloat locations[] = { 0, 1.0 };
CGGradientRef gradient = CGGradientCreateWithColorComponents(space, colors, locations, 2);
CGPoint startPoint = glossRect.origin;
CGPoint endPoint = CGPointMake(glossRect.origin.x, glossRect.origin.y + glossRect.size.height);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);

//Gradient Stroke Gloss Path
CGContextAddPath(context, glossPath);
CGContextSetLineWidth(context, 2);
CGContextReplacePathWithStrokedPath(context);
CGContextClip(context);
CGFloat colors2[] =
{
1, 1, 1, .3,
1, 1, 1, .1,
1, 1, 1, .0,
};
CGFloat locations2[] = { 0, .1, 1.0 };
CGGradientRef gradient2 = CGGradientCreateWithColorComponents(space, colors2, locations2, 3);
CGPoint startPoint2 = glossRect.origin;
CGPoint endPoint2 = CGPointMake(glossRect.origin.x, glossRect.origin.y + glossRect.size.height);
CGContextDrawLinearGradient(context, gradient2, startPoint2, endPoint2, 0);

//Cleanup
CGPathRelease(path);
CGPathRelease(glossPath);
CGColorSpaceRelease(space);
CGGradientRelease(gradient);
CGGradientRelease(gradient2);
}

- (CGFloat)yShadowOffset {
if (!_yShadowOffset) {
float osVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if (osVersion >= 3.2) {
_yShadowOffset = 6;
} else {
_yShadowOffset = -6;
}

}
return _yShadowOffset;
}

- (CGFloat)relativeParentXPosition {
CGPoint parentOrigin = [self.mapView convertPoint:self.parentAnnotationView.frame.origin
fromView:self.parentAnnotationView.superview];
return parentOrigin.x + self.offsetFromParent.x;
}
Note: in iOS 3.2 CGContextSetShadowWithColor reversed the direction of the y-axis offset, thus requiring theyShadowOffset method seen above.



Let’s Add Some Content
To allow the addition of content we will create a content view as a read-only property, which will allow our consumers to access it. An additional method, prepareContentFrame will be added and invoked from setAnnotation: to set the content frame.

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)setAnnotation:(id )annotation {
[super setAnnotation:annotation];
[self prepareFrameSize];
[self prepareOffset];
[self prepareContentFrame];
[self setNeedsDisplay];
}

- (void)prepareContentFrame {
CGRect contentFrame = CGRectMake(self.bounds.origin.x + 10,
self.bounds.origin.y + 3,
self.bounds.size.width - 20,
self.contentHeight);

self.contentView.frame = contentFrame;
}

- (UIView *)contentView {
if (!_contentView) {
_contentView = [[UIView alloc] init];
self.contentView.backgroundColor = [UIColor clearColor];
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[self addSubview:self.contentView];
}
return _contentView;
}


In our map view controller we will add the following code in mapView:viewForAnnotation to place an image in the callout and set the proper content height.

?
1
2
3
4
5
calloutMapAnnotationView.contentHeight = 78.0f;
UIImage *asynchronyLogo = [UIImage imageNamed:@"asynchrony-logo-small.png"];
UIImageView *asynchronyLogoView = [[[UIImageView alloc] initWithImage:asynchronyLogo] autorelease];
asynchronyLogoView.frame = CGRectMake(5, 2, asynchronyLogoView.frame.size.width, asynchronyLogoView.frame.size.height);
[calloutMapAnnotationView.contentView addSubview:asynchronyLogoView];


Animation
So far the callout looks similar to the native callout, but it is still lacking some of the behavior of the original. The callout needs to animate out from the parent annotation. Also, when the parent annotation is near the edge of the map view, the map should be adjusted to move the parent annotation in from the edge of the view.

The animation would be fairly simple if we could just adjust the frame of the callout view, however that will not scale the contents of the callout. Thus, we must use a CGAffineTransform. Apple has a good introducton to affine transforms. The transform will need to both scale the view and translate the view to make it appear to grow out of the parent annotation. Scaling is simple – a value of 1 is normal size and other values act as a multiplier, so smaller values shrink the view and larger values expand the view. If the parent is off-center on the x-axis the callout needs to be translated to keep the point fixed directly over the parent annotation. Likewise the y-axis must be translated so that it appears that the callout grows upward from parent. We need to hold on to the frame for these calculations because self.frame cannot be trusted during the animations. The calculations are done in the following two methods:

?
1
2
3
4
5
6
7
8
9
- (CGFloat)xTransformForScale:(CGFloat)scale {
CGFloat xDistanceFromCenterToParent = self.endFrame.size.width / 2 - [self relativeParentXPosition];
return (xDistanceFromCenterToParent * scale) - xDistanceFromCenterToParent;
}

- (CGFloat)yTransformForScale:(CGFloat)scale {
CGFloat yDistanceFromCenterToParent = (((self.endFrame.size.height) / 2) + self.offsetFromParent.y + CalloutMapAnnotationViewBottomShadowBufferSize + CalloutMapAnnotationViewHeightAboveParent);
return yDistanceFromCenterToParent - yDistanceFromCenterToParent * scale;
}


There will be three steps to the animation to create the bounce-like effect of the standard callout. We cannot begin the animation with a scale of 0 because a transformation matrix with a scale of 0 cannot be inverted.

Grow from very small to slightly larger than the final size
Shrink to slightly smaller than the final size
Grow to the final size
These three steps will be separate animations chained together using UIView’s setAnimationDidStopSelector: and setAnimationDelegate: methods.

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
- (void)animateIn {
self.endFrame = self.frame;
CGFloat scale = 0.001f;
self.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, [self xTransformForScale:scale], [self yTransformForScale:scale]);
[UIView beginAnimations:@"animateIn" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
[UIView setAnimationDuration:0.075];
[UIView setAnimationDidStopSelector:@selector(animateInStepTwo)];
[UIView setAnimationDelegate:self];

scale = 1.1;
self.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, [self xTransformForScale:scale], [self yTransformForScale:scale]);

[UIView commitAnimations];
}

- (void)animateInStepTwo {
[UIView beginAnimations:@"animateInStepTwo" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.1];
[UIView setAnimationDidStopSelector:@selector(animateInStepThree)];
[UIView setAnimationDelegate:self];

CGFloat scale = 0.95;
self.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, [self xTransformForScale:scale], [self yTransformForScale:scale]);

[UIView commitAnimations];
}

- (void)animateInStepThree {
[UIView beginAnimations:@"animateInStepThree" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.075];

CGFloat scale = 1.0;
self.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, [self xTransformForScale:scale], [self yTransformForScale:scale]);

[UIView commitAnimations];
}


Shifting the Map
When the parent annotation is near the edge of the map, the map needs to be shifted so that the parent annotation and the callout remain a certain distance away from the edge of the view. To do this we need to calculate the distance to the edge of the view, the number of degrees latitude and longitude per pixel, and then set the new center point for the map. This adjustment should be made when didMoveToSuperview is called.


- (void)adjustMapRegionIfNeeded {
//Longitude
CGFloat xPixelShift = 0;
if ([self relativeParentXPosition] < 38) {
xPixelShift = 38 - [self relativeParentXPosition];
} else if ([self relativeParentXPosition] > self.frame.size.width - 38) {
xPixelShift = (self.frame.size.width - 38) - [self relativeParentXPosition];
}

//Latitude
CGPoint mapViewOriginRelativeToParent = [self.mapView convertPoint:self.mapView.frame.origin toView:self.parentAnnotationView];
CGFloat yPixelShift = 0;
CGFloat pixelsFromTopOfMapView = -(mapViewOriginRelativeToParent.y + self.frame.size.height - CalloutMapAnnotationViewBottomShadowBufferSize);
CGFloat pixelsFromBottomOfMapView = self.mapView.frame.size.height + mapViewOriginRelativeToParent.y - self.parentAnnotationView.frame.size.height;
if (pixelsFromTopOfMapView < 7) {
yPixelShift = 7 - pixelsFromTopOfMapView;
} else if (pixelsFromBottomOfMapView < 10) {
yPixelShift = -(10 - pixelsFromBottomOfMapView);
}

//Calculate new center point, if needed
if (xPixelShift || yPixelShift) {
CGFloat pixelsPerDegreeLongitude = self.mapView.frame.size.width / self.mapView.region.span.longitudeDelta;
CGFloat pixelsPerDegreeLatitude = self.mapView.frame.size.height / self.mapView.region.span.latitudeDelta;

CLLocationDegrees longitudinalShift = -(xPixelShift / pixelsPerDegreeLongitude);
CLLocationDegrees latitudinalShift = yPixelShift / pixelsPerDegreeLatitude;

CLLocationCoordinate2D newCenterCoordinate = {self.mapView.region.center.latitude + latitudinalShift,
self.mapView.region.center.longitude + longitudinalShift};

[self.mapView setCenterCoordinate:newCenterCoordinate animated:YES];

//fix for now
self.frame = CGRectMake(self.frame.origin.x - xPixelShift,
self.frame.origin.y - yPixelShift,
self.frame.size.width,
self.frame.size.height);
//fix for later (after zoom or other action that resets the frame)
self.centerOffset = CGPointMake(self.centerOffset.x - xPixelShift, self.centerOffset.y);
}
}


Conclusion
It takes a bit of work to replicate the iOS map annotation callout, but it is worth the effort if you need a larger space for content. You may download the full source code to see a working example.

Monday, February 28, 2011

Deploying iPhone Apps to Real Devices

In our previous article on getting started with iPhone development, you learnt how to use the iPhone SDK provided by Apple to develop your first iPhone application. For testing purposes, you used the iPhone Simulator, provided as part of the iPhone SDK. While the iPhone Simulator is a very handy tool that allows you to test your iPhone applications without needing a real device, nothing beats testing on a real device. This is especially true when you are ready to roll out your applications to the world - you must ensure that it works correctly on real devices. In addition, if your application requires accesses to hardware features on an iPhone/iPod Touch, such as the accelerometer and GPS, you need to test it on a real device - the iPhone Simulator is simply not adequate.

A repeated criticism from iPhone app developers comes from the difficulty they find in deploying their application to a real iPhone or iPod Touch. Apple, for better or worse, has designed a process involving many hoops that must be jumped through, and this has prompted developers to grumble, and others to explore alternative, non-official open tool chains, which do not require app signing. In this article, I will walk you through the steps you need to take in order to test your iPhone apps on a real device, be it iPhone, or iPod Touch, the offical Apple way.

Sign up for the iPhone Developer Program
The first step towards testing your applications on a real device is to sign up for the iPhone Developer Program at http://developer.apple.com/iphone/program/. There are two programs available - Standard and Enterprise. For most developers wanting to release applications on the App Store, they can simply sign up for the Standard program, which costs US$99. Check out http://developer.apple.com/iphone/program/apply.html to know more about the differences between the Standard and Enterprise programs.

Start your Xcode
In order to test your iPhone applications on your device, you need to obtain an iPhone Development Certificate from the iPhone Developer Program Portal. This needs to be done once for every device you wish to test your apps on. The following sections walk you through the various steps, from obtaining your certificate, to deploying your applications onto the device.

First, obtain the 40-character identifier that uniquely identitfies your iPhone/iPod Touch. To do so, connect your device to your Mac and start Xcode. Select the Window > Organizer menu item to launch the Organizer application. Figure 1 shows the Organizer application showing the identifier of my iPhone. Copy this identifier and save it somewhere. You will need it later on.


Figure 1 Obtaining the identifier for your iPhone/iPod Touch

Generating a Certificate Signing Request
Before you can request a development certificate from Apple, you need to generate a Certificate Signing Request. This step must be performed once for every device you wish to test on. To generate the request, you can use the Keychain Access application located in the Applications/Utilities/ folder (see Figure 2).


Figure 2 Launching the Keychain Access application

In the Keychain Access application, select the Keychain Access > Certificate Assistant menu and select Request a Certificate From a Certificate Authority (see Figure 3).


Figure 3 Requesting a certificate

In the Certificate Assistant window (see Figure 4), enter your email address, check the Saved to disk radio button and check the Let me specify key pair information checkbox. Click Continue.


Figure 4 Providing certificate information

Choose a key size of 2048 bits and use the RSA algorithm (see Figure 5). Click Continue.


Figure 5 Specifying key pair information

You will be asked to save the request to a file. Use the default name suggested and click Save (see Figure 6).


Figure 6 Saving the certificate request to a file

Logging in to the iPhone Developer Program Portal
Once you have generated the certificate signing request, you need to login to Apple's iPhone Dev Center (see Figure 7). Click on the iPhone Developer Program Portal link on the right of the page. Remember, you need to pay US$99 in order to access this page.


Figure 7 Logging in to the iPhone Dev Center

In the iPhone Developer Program Portal page, click the Launch Assistant button (see Figure 8) to walk you through the process of provisioning your iPhone and generating the development certificate.


Figure 8 Launching the provisioning assistant

You should see the welcome page as shown in Figure 9. Click Continue.


Figure 9 The welcome page of the provisioning assistant

First, you will be asked to create an App ID (see Figure 10). An App ID is a series of characters used to uniquely identify an application (or applications) on your iPhone. You only need to create an App ID once per application, i.e. you do not need a new App ID for new versions of your app. Enter a friendly name to describe this App ID (to be generated by Apple). Click Continue.


Figure 10 Creating an App ID

The next screen allows you to provide a description of your iPhone/iPod Touch. You need to provide the device ID that you have obtained earlier (see Figure 11). Click Continue.


Figure 11 Assigning a device for the provisioning

You are now ready to submit the certificate signing request to Apple (see Figure 12). The instructions on the screen show you the steps that you have performed earlier. Click Continue.


Figure 12 Generating a certificate signing request

In this screen, click the Choose File button (see Figure 13) to select the certificate signing request file that you have created earlier. Once the file has been selected, click Continue.


Figure 13 Submitting a certificate signing request

Provide a description for your provisioning profile (see Figure 14). A Provisioning profile will be generated so that you can download it at a later stage and install it on your device. Click Generate.


Figure 14 Naming your provisioning profile

A Provisioning profile will now be generated (see Figure 15). Once it is generated, click Continue.


Figure 15 Generating a provisioning profile

You are now ready to download the generated Provisioning profile onto your Mac (see Figure 16). Click Continue.


Figure 16 Downloading and installing your provisioning profile

Drag and drop the downloaded Provisioning profile (in the Downloads folder) onto Xcode (located in the Dock). This will install the Provisioning profile onto your connected iPhone/iPod Touch. Click Continue (see Figure 17).


Figure 17 Instructions to verify the installation

You can verify that the Provisioning profile is installed correctly on your device by going to the Organizer application and viewing the Provisioning section (see Figure 18) to see if the profile has been added.


Figure 18 Verifying the provisioning profile on the Organizer

Back in the iPhone Developer Program Portal, you are now ready to download and install the development certificate onto your iPhone/iPod Touch. Click the Download Now button (see Figure 19) to download the development certificate to your Mac. Click Continue.


Figure 19 Downloading and installing the development certificate

In the Downloads folder of your Mac, double-click on the developer_identify.cer file that you have just downloaded to install it into a keychain on your Mac. When prompted (see Figure 20), click OK.


Figure 20 Adding a certificate to the keychain

Back in the iPhone Developer Program Portal, you can now verify that the certificate has been installed properly in the Keychain Access application (see Figure 21). Click Continue.


Figure 21 Instructions for verifying the installation of the certificate

In the Keychain Access application, select the login keychain and look for the certificate named "iPhone Developer:" (see Figure 22). If you can see it there, your certificate is installed correctly.


Figure 22 Verifying the installation of the certificate

You are now almost ready to deploy your iPhone application onto your iPhone/iPod Touch. Click Continue (see Figure 23).


Figure 23 Instructions for installing your applications with Xcode

Click Done to dismiss the dialog (see Figure 24).


Figure 24 Finishing the installation

In Xcode, under the Active SDK item (if this item is not already on the toolbar, go to View > Customize Toolbar and add it to the toolbar), select the OS version number of the device that is currently connected to your Mac. In my case, my iPhone is running the older iPhone OS 2.0, hence I selected "iPhone Device (2.0)" (see Figure 25).


Figure 25 Selecting the active SDK

With your application project open in Xcode, press Command-R to run the application. You will now be prompted for permission to access the certificate saved in your keychain. Click Allow (or Always Allow) to go ahead with the signing (see Figure 26).


Figure 26 Signing your application with the certificate

Your application will now be deployed to the device. You can see its progress in the Summary tab of the Organizer application (see Figure 27).


Figure 27 Checking the Organizer for installation progress

Once the application is deployed onto your device, it will be running automatically. You can capture screenshots of your device by going to the Screenshot tab of the Organizer application, and pressing the capture button (see Figure 28).


Figure 28 Capturing screenshots using Organizer

Summary
In this article, you have seen the various steps required to deploy your application to your iPhone or iPod Touch. While the number of steps looked intimidating, it is actually quite straightforward. The iPhone Developer program allows you to provision up to 100 devices for testing purposes. Once a device is provisioned, you can then use the development certificate to deploy your applications onto it.

Creating Circular and Infinite UIScrollViews

When creating paging functionality for iPhone apps, there may be times that an infinite page loop would be desired. For example, if you have a small gallery of photos you are displaying, you may want to swipe through the set and have it start back at the beginning once you reach the end. The user would be able to continue swiping as much as they wanted in one direction to continue to view the content. Here are two strategies for achieving this result:
Duplicate End Caps
The first option is best suited for smaller loops. Suppose you have ten photos to display. When the user is on photo one and swipes left, it will take the user to photo ten. When the user is on photo ten and swipes right, it will take the user to photo one. The logic we will follow here is to add photos in order, but place an duplicate of photo ten to the left of photo one and a duplicate of photo one to the right of photo ten.

Now when the user scrolls to the end, we reposition the content offset of our UIScrollView. By having a duplicate photo at the end and repositioning the offset without using animation, we create a seamless experience for the user.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
// The key is repositioning without animation
if (scrollView.contentOffset.x == 0) {
// user is scrolling to the left from image 1 to image 10.
// reposition offset to show image 10 that is on the right in the scroll view
[scrollView scrollRectToVisible:CGRectMake(3520,0,320,480) animated:NO];
}
else if (scrollView.contentOffset.x == 3840) {
// user is scrolling to the right from image 10 to image 1.
// reposition offset to show image 1 that is on the left in the scroll view
[scrollView scrollRectToVisible:CGRectMake(320,0,320,480) animated:NO];
}
}
3 Pages Only
There may be times when you want an infinite page loop, but don’t want to load in a lot of content. For example, You may have a lot of content to display inside the UIScrollView. Loading large amounts of data there would not be the ideal approach to the situation.
The logic that we can use there is to keep the UIScrollView at just three pages. Data would load on each page and the user would always be looking at the data in the middle page. When the user scrolled to a new page, the content for each page would be reset and the offset would go back the user is back to viewing the middle page. That way even though you may have a large amount of data to scroll through, it’s not all loaded at once. Only three pages are ever loaded at one time.

- (void)viewDidLoad
{
[super viewDidLoad];

documentTitles = [[NSMutableArray alloc] init];

// create our array of documents
for (int i = 0; i < 10; i++) {
[documentTitles addObject:[NSString stringWithFormat:@"Document %i",i+1]];
}

// create placeholders for each of our documents
pageOneDoc = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
pageTwoDoc = [[UILabel alloc] initWithFrame:CGRectMake(320, 0, 320, 44)];
pageThreeDoc = [[UILabel alloc] initWithFrame:CGRectMake(640, 0, 320, 44)];

pageOneDoc.textAlignment = UITextAlignmentCenter;
pageTwoDoc.textAlignment = UITextAlignmentCenter;
pageThreeDoc.textAlignment = UITextAlignmentCenter;

// load all three pages into our scroll view
[self loadPageWithId:9 onPage:0];
[self loadPageWithId:0 onPage:1];
[self loadPageWithId:1 onPage:2];

[scrollView addSubview:pageOneDoc];
[scrollView addSubview:pageTwoDoc];
[scrollView addSubview:pageThreeDoc];

// adjust content size for three pages of data and reposition to center page
scrollView.contentSize = CGSizeMake(960, 416);
[scrollView scrollRectToVisible:CGRectMake(320,0,320,416) animated:NO];
}

- (void)loadPageWithId:(int)index onPage:(int)page
{
// load data for page
switch (page) {
case 0:
pageOneDoc.text = [documentTitles objectAtIndex:index];
break;
case 1:
pageTwoDoc.text = [documentTitles objectAtIndex:index];
break;
case 2:
pageThreeDoc.text = [documentTitles objectAtIndex:index];
break;
}
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
// All data for the documents are stored in an array (documentTitles).
// We keep track of the index that we are scrolling to so that we
// know what data to load for each page.
if(scrollView.contentOffset.x > scrollView.frame.size.width)
{
// We are moving forward. Load the current doc data on the first page.
[self loadPageWithId:currIndex onPage:0];

// Add one to the currentIndex or reset to 0 if we have reached the end.
currIndex = (currIndex $gt;= [documentTitles count]-1) ? 0 : currIndex + 1;
[self loadPageWithId:currIndex onPage:1];

// Load content on the last page. This is either from the next item in the array
// or the first if we have reached the end.
nextIndex = (currIndex $gt;= [documentTitles count]-1) ? 0 : currIndex + 1;

[self loadPageWithId:nextIndex onPage:2];
}
if(scrollView.contentOffset.x $lt; scrollView.frame.size.width) {
// We are moving backward. Load the current doc data on the last page.
[self loadPageWithId:currIndex onPage:2];

// Subtract one from the currentIndex or go to the end if we have reached the beginning.
currIndex = (currIndex == 0) ? [documentTitles count]-1 : currIndex - 1;
[self loadPageWithId:currIndex onPage:1];

// Load content on the first page. This is either from the prev item in the array
// or the last if we have reached the beginning.
prevIndex = (currIndex == 0) ? [documentTitles count]-1 : currIndex - 1;

[self loadPageWithId:prevIndex onPage:0];
}

// Reset offset back to middle page
[scrollView scrollRectToVisible:CGRectMake(320,0,320,416) animated:NO];
}
Download the Source Code
Xcode Project – Circular UI Scrollview

Thursday, February 24, 2011

iPhone Images from URL using XML File

Introduction

This article is about displaying images on iPhone from web using XML file as medium. In this article, we learn two things. So let’s deal with this article in two phases:

Parsing XML File in iPhone
Display images from Web URL
Parsing XML Files in iPhone (NSXML Library)

Apple has provided NSXMLParser for parsing XML files in iPhone project. This parser takes a web URL as input path (file should reside in the web) and parses the document. Delegate methods of the NSXMLParser do the remaining work for you.

The below three delegate methods of the NSXMLParser class are used to parse the XML files.

didStartElement
didEndElement
foundCharacters
didStartElement

This method is sent by parser object to its delegate when it sees the starting tag of the given element. The syntax is as follows:

Collapse
- (void)parser:(NSXMLParser*)parser didStartElement:(NSString *)
elementName namespaceURI:(NSString *)namespaceURI qualifiedName:
(NSString *)qName attributes:(NSDictionary *)attributeDict
didEndElement

This method is sent by parser object to its delegate when it sees the end tag of the given element. The syntax is as follows:

Collapse
-(void)parser:(NSXMLParser*)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString*)
namespaceURI qualifiedName:(NSString *)qName
foundCharacters

This method is sent by parser object to its delegate when it finds the characters between start tag and end tag of the given element. The syntax is as follows:

Collapse
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
UIImage from URL

Displaying an image from the URL is a 3 step process:

Create an NSURL Object from web URL
Load the NSURL object with image data in NSData Object
Assign the image data NSData to the UIImage
Collapse
NSURL *url = [NSURL
URLWithString:@”www.ArticleDean.com\images\sample.jpg”];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [[UIImage alloc] intiWithData:data];
This is the minimum knowledge we need before we are going to the original application. Let’s start coding our application.

Step 1: Create a view based application, and name the project as XMLWebImages.

Step 2: Open XMLWebImagesViewController.h, add IBOutlet type object scrollView of UIScrollView. Synthesize the view object scrollView in XMLWebImagesViewController.m.

Step 3: Create a new class inherited from UIViewSubclass and name it as XMLWebView and add a UIImageView object as IBOutlet and call it imageView. Call getters and setters and synthesize imageView Object.

Collapse
#import
@interface XMLWebImageViewController : UIViewController
{
IBOutlet UIScrollView *webScrollView;
}
@property (nonatomic, retain) IBOutlet UIScrollView * webScrollView;
@synthesize scrollView;
Step 3: Create a new class inherited from UIViewSubclass and name it as XMLWebView and add a UIImageView object as IBOutlet and call it imageView. Call getters and setters and synthesize imageView Object.

Collapse
@interface XMLWebView : UIView
{
IBOutlet UIImageView *imageView;
}
@property (nonatomic, retain) IBOutlet UIImageView *imageView;
@synthesize imageView;
Step 4: Create a .XIB interface builder file and connect the imageView object to the XMLWebView in the document window to show our images.

Step 5: Create an XMLWebElement class inherited from NSObject to hold the UIImage object to hold the web images.

Collapse
@interface XMLWebElement: NSObject
{
UIImage *imgXML;
}
@property (nonatomic, retain) UIImage * imgXML;
@end
@synthesize image;
Step 6: Now the time has come to start the parsing and take the images from XML residing in articledean.com. Open XMLWebImagesViewController.h file and create an object of NSXMLParser, and collections of XML items into an array of XMLWebElements and a temp XMLWebElement object.

Collapse
@interface XMLWebImagesViewController: UIViewController
{
IBOutlet UIScrollView *scrollview;
NSXMLParser *xmlParser;
NSMutableString *currentNode;
NSMutableArray *xmlElements;
XMLWebElement *tempElement;
}
Write setters and getters for this and synthesize these objects in XMLWebImagesViewController.m file.

Now in viewDidLoad method, allocate memory for xmlElements and assign the parser with the web XML file.

Collapse
- (void)viewDidLoad
{
[super viewDidLoad];
xmlElements = [[NSMutableArray alloc] init];
xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:
[NSURL URLWithString:@"http://www.articledean.com/images.xml"]];
[xmlParser setDelegate:self];
[xmlParser parse];
}
Now it's time to add delegate methods of NSXMLParser and I am introducing a sample XML file structure here.

Collapse


http://www.articledean.com/testimage1.jpg


http://www.articledean.com/testimage2.jpg


Now fill the delegate methods to retrieve the image URL from web and load the XMLWebElements array.

Collapse
- (void)xmlParser:(NSXMLParser *)xmlParser didStartElement:
(NSString *)elementName namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
if(![elementName compare:@"PictureInfo"])
{
tempElement = [[iCodeBlogXMLElement alloc] init];
}

else if(![elementName compare:@"imageURL"])
{
currentAttribute = [NSMutableString string];
}

else if(![elementName compare:@"imageTitle"])
{
currentAttribute = [NSMutableString string];
}
}
Collapse
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)
elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if(![elementName compare:@"PictureInfo"])
{
[xmlElementObjects addObject:tempElement];
}
else if(![elementName compare:@"imageURL"])
{
NSURL *imageURL = [NSURL URLWithString:currentAttribute];
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [[UIImage alloc] initWithData:data];
[tempElement setImage:image];
}
else if(![elementName compare:@"imageTitle"])
{
NSLog(@"The image title is %@", currentAttribute);
[tempElement setImageTitle:currentAttribute];
}
else if(![elementName compare:@"Pictures"])
{
[self layoutSubview];
}
}
Collapse
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if(self.currentAttribute)
{
[self.currentAttribute appendString:string];
}
}
We are almost done with the code. Remaining part is a cake walk. Now the arrays are filled with the images URLs. Traverse though the array and assign the images to UIView and display them in the scroll view.

References

http://www.ArticleDean.com
http://icodeblog.com
History

8th May, 2010: Initial post
License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Thursday, February 17, 2011

How to use sqlite with UITableView in iPhone SDK

Hi All,
I have been creating an App using TableView with SQLite Manager….As a Beginner Like many others I too visited many sites in search of understanding about tableviews and how to Integrate it with Database….Here I am giving you all a detailed explanation of about TableViews and Database….let us look at it….
AIM OF THE Project: Here our Aim is Afterwhen launching into the Application you get list of Animals names Displayed and when you click on those animal names you get details of those animals in the next view.
So Initially for developing any project you must have:
Database —- At first we must have our database ready in our Hand in order to make our lifes easy for using that in our Application.
Now let us look at how to create Database using SQLite Manager….
Foreign key (Missing):
SQlite Manager: Is easy to use and create database but the only issue with it is THERE IS NO FOREIGN KEY OPTION….Foreign keys Constraint is Missing in SQLite Manager.
SQLite3: In the NEW VERSION of SQLite 3.6.19 and latest versions they have added the Foreign key concepts in them. But remember SQLITE3 is Different from SQLite Manager.
Download SQLite Manager Add-on from FireFox and then Open Firefox > Tools(Menu) > SQLite Manager

Then Menu (In SQLite Manager)> (click) NewDatabase > SampleDB( Name of Database )
Then select the database from the Directory (option on the top, Go to your Folder) > SampleDB(select your Database)
After that From the Menu on the Top in SQLite Manager you will find Different options for Creating Table, Editing Table etc….so from that Click Create Table and then create tables after that Insert Data into those using “Insert Into” Statement into those tables….
Inserting data into those tables We Must go to EXECUTE SQL (Option in SQLite Manager ) > Write Queary for Inserting data into the table > RunSQL
In this App for SampleDB, I wrote two Tables called:
1) Animals 2) Descsription —- here is how the database look like….
So We have completed Creating the DataBase for our Project,now we will be jumping into X-Code for Developing the App….
TableViews:
In Table Views the most important thing we need to keep track is switching between the Views….In my Project I have two views
1) For the First View I am inserting the data Manually
2) For the Second View I am Inserting data into tableview from the DataBase.
so Initially Go to X-Code > iPhoneOS > Navigational-based Applications > SampleTableViewApp (Name of The App) > Choose
Now you see the Couple of Files Generated for us….
Here you see 1) RootViewController.h 2) RootViewController.m 3) SampleTableViewAppDeligate.h 4) SampleTableViewAppDeligate.m 5) RootViewController.xib
These are the Files generated by X-Code and you can see those files in Classes Folder and Resources Folder.
Adding SQLite3 lib file: Before starting the code we have to Add the SQLite3 Library into the Framework in the Application.
(right Click) Frameworks > Add File > Add Existing Frameworks > LibSqlite3.0.dylib > Add
FMDB Wrapper: I am using FMDB Wrapper as an alternative for SQLite3 as it has easy way of steps for accessing the DataBase. You can download this wrapper from Google. After downloading JUST DRAG THE FMDB FOLDER INTO YOUR APPLICATION.
Adding Database: Next Step is Add Database file to your Application
Go to your DataBase folder where you stored your SampleDB > Drag it in to your App ( You can store it in anywhere )
Coding: Let US Start coding the App…..so
The Next Step is We have Got the Files above and now Go to:
(right click on) SampleTableViewApp > Add > NewFile > UIViewController SubClass > Next > ElephantViewController.m (Name of File) > Click Checkbox to add .h Files as shown in Figure.
In The Same way Add the LionViewController file….

Don’t get confused with the RootViewController,Deligate file, ElephantViewControllerfiles, LionViewController…. I will Explain you all those….
RootViewController: It is the First View of our App
ElephantViewController, LionViewController: These are the Detailed View of the items which are displayed in the first view
Deligete Files: This is the File which have all the details of deligate methods of TableView.
So now we have our RootView files and the DetailedView file of the Root View….Now we will code our RootViewController File:
Steps:
1) RootViewController.h : Import “AnimalViewController” , then Create An Mutable Array for storing the Elements. Here is the code below:
#import <UIKit/UILit.h>
#import "ElephantViewController.h"
#import "LionViewController.h"

@interface RootViewController : UITableViewController
{

NSMutableArray *mAnimal;

}

@end
2) Go to RootViewController.m: then add the below code in View Did Load Function:
Here we are adding objects in the Array and Setting Title to the First View of The App….Every thing is explained in the Code:
- (void)viewDidLoad
{
[super viewDidLoad];

/* Here we have allocated some memory to the Array object so we have to release it in Dealloc Function */

mAnimal = [[NSMutableArray alloc] init];

/* Next Step is Adding Objects to the Array*/

[mAnimal addObject:@"Elephant"];
[mAnimal addObject:@"Lion"];

/* Setting Title to the First View */

[self setTitle:@"Quote Category"];

}
Modifying TableView Deligate Methods:
3) In this step we have to Modify the Deligate Methods of the Tableview according to our App Requirements:
These can be found in the Bottom after the ” #pragma mark Table view methods” Line. so Add the Below Code in those Methods:
Here the First Method we have to change is “NumberOfRowsInSection” by default it is “0″ but We want our Rows to be Displayed as the number of elements in the Array so Update your Code as shown below:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [mAnimal count];
}
4) In this step we can Configure the cells in the TableView by changing this Method “cellForRowAtIndexPath”
we can set the Accessory type like” > ” as shown in the iPhone or iPod:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

/* These are the Default Methods we don't need to configure them */

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
/* Configure the cell.
We have to start configuring cells from here
*/

[cell setText:[[qCategoriesList objectAtIndex:indexPath.row] retain]];

/* set the Accessory Type to our Cells like ">"*/

[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];

return cell;
}
5) Next Most Important step is “Pushing the First View to Second View” for this uncomment the “DidSelectRow” and do the following code:
// Override to support row selection in the table view.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

/* all the Methods are already given in this Function, We just need to change them according to our Requirements
If we Select Elephant then the View has to Push into another View giving details abuot the Elephant */

// Elephant View

if([[mAnimal objectAtIndex:indexPath.row] isEqual:@"Elephat"])
{
ElephantViewController *mEle = [[ElephantViewController alloc] initWithNibName:@"ElephantViewController" bundle:nil];
//Setting title to the TableView
[mEle setTitle:@"Elephant Details"];
//Pushing the View from RootView to ElephantView
[self.navigationController pushViewController:mEle animated:YES];
//releasing the Allocated Object
[mEle release];
}

// Lion View

if([[mAnimal objectAtIndex:indexPath.row] isEqual:@"Lion"])
{
LionViewController *mLion = [[LionViewController alloc] initWithNibName:@"LionViewController" bundle:nil];

//Setting title to the TableView
[mLion setTitle:@"Lion Details"];

//Pushing the View from RootView to ElephantView
[self.navigationController pushViewController:mLion animated:YES];

//releasing the Allocated Object
[mLion release];
}
/* There ends the Pushing of our view now we have to code in our detailed views of Elephant and Lion in order to display data about them----we are displaying this data from the Database */

// Navigation logic may go here -- for example, create and push another view controller.
// AnotherViewController *anotherViewController = [[AnotherViewController alloc] initWithNibName:@"AnotherView" bundle:nil];
// [self.navigationController pushViewController:anotherViewController animated:YES];
// [anotherViewController release];
}
Now we have to Code Our Detailed Views and Display data in them…We first Code our ElephantViewController
6) Now As we have created our “ElephatViewController” as a SubClass of UIViewController we need to Change that to UITableViewController Class
i:e “ElephantTableViewController” as we want to display data in a TableView in ElephantViewController. Here is the Code of that:
/* We have to import the FMDB Headers in our file as we have to use its queary methods for accessing the DB*/
#import
#import "FMDatabase.h"
#import "FMResultSet.h"

/*
It is Like this
@interface ElephantViewController : UIViewController
Now we are going to change it like this:
*/
@interface ElephantViewController : UITableViewController
{
// We have to Create an Array in order to Hold the Data coming from the DataBase
NSMutableArray *aElephant;
//NSString object is used to hold the elements retrived from the database
NSString *eleData;
}
@property (nonatomic,retain) NSString* eleData;
@end
7) Now as we did the .H file we have to implement the database connection and get the results form the DB
here how it goes: now go to ElephantViewController.m File then do this code….
#import "ElephantViewController.h"

@implementation ElephantViewController

@synthesize eleData;

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.

- (void)viewDidLoad
{
[super viewDidLoad];

/* Now here we have to write the code from the database to display the details */

aElephant = [[NSMutableArray alloc] init];

/*
Here we have to set the path
We are creating the path for the datbase file in order to access
*/

NSString *path = [[NSBundle mainBundle] pathForResource:@"SampleDB" ofType:@"sqlite"];

/* FMDatabase here is used to take the database path */

FMDatabase *db = [[FMDatabase alloc] initWithPath:path];

/* Here we are opening the datase in order to access it */

[db open];

/*
Then the next step here is taking the datbase result using the sqlqueary and then carrying that result into the resultset object
Resultset for Elephant: select * from Description where a_id=1; here description is the table, a_id is the id of Elephant it is
the primary key in animals table
*/

FMResultSet *fResult= [db executeQuery:@"SELECT * FROM description WHERE a_id=1"];

/* Now we have to take these results in to a loop and then repeteadly loop it in order to get all the data in a our Array Object */

while([fResult next])
{
/* taking results from database to a string "eleData" */
eleData = [fResult stringForColumn:@"desc"];
/* adding data from the string object to Array */
[aElephant addObject:eleData];
/* Checking weather data has come or not */
NSLog(@"The data is %@=",eleData);
}
/* Closing the Database */
[db close];
}
And now we got our data form the Database and stored it in the array Element
In this Step as we got our data we have to now display that data in the table view of ElephantViewController.
For that we need deligate methods of TableView….Just Go to UITableView and copy the methods from there under the” #pragma mark Table view methods”….
So now we only copy the required methods of Deligate….methods with code are shown below, we don’t need to copy the “DidSelectRow”
as we are not pushing this view to another view…we are just displaying the data here…. copy deligate methods above the dealloc():
/* UITABLEVIEW Deligate Methods */

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}

// Customize the number of rows in the table view.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [aElephant count];
}

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell.
//We are configuring my cells in table view

NSString *eData = [aElephant objectAtIndex:indexPath.row];
cell.textLabel.text = eData;

return cell;
}
So Finally we did the Code part now we have to do the Interface builder for the ElephantViewController….
Remember in the above RootViewController we did not do any interface builder why because by default the TableView is added to it….as initially we selected
“Navigation based Application”….
For adding the TableView in the ElephantViewController (click)ElephantViewController.xib > then drop TableView on to the View Window.
Important Info:
In the Connection we have to connect the datasource and deligate to the FilesOwner in the Interface Builder.
9) We have completed the Detailed View of ElephantViewController now we have to do the LionViewController:
Repeat the same code for LionView Controller after doing that Save the files and Interface Builder….
X-Code > Build > Go
This is the Final Result of our Application:
RootViewController View
Detailed View of ElephantViewController
Detailed View of LionViewController
And So Thus our Project gets Succeeded….Hope this article helps you….
And do comment on this post if you liked it….Finally Subscribe US(Object Graph) on FaceBook and Twitter….
Project Download:
You can Download Project by clicking here

Wednesday, February 16, 2011

SplitView application in iPad

SplitView application in iPad
On April 21, 2010, in iPad Development, by Sushant
This is the SplitView application. Split View application is applicable only for the iPad devices. We will see here how to use Split View in the application. We are using here UISplitViewController for displaying two side by side view controller. Left side view controller presents a list of item and right side view controller presents details of the selected item. So let see how it will be worked.

This is the SplitView application. Split View application is applicable only for the iPad devices. We will see here how to use Split View in the application. We are using here UISplitViewController for displaying two side by side view controller. Left side view controller presents a list of item and right side view controller presents details of the selected item. So let see how it will be worked.

Step 1: Open the Xcode Create a new project using a Split View-base application template. Give the application name “SplitView”.



Figure 1: SplitView template

Step 2: Xcode automatically creates the directory structure and adds essential frameworks to it. You can explore the directory structure to check out the content of the directory.

Step 3: xpand classes and notice Interface Builder created the RootViewController and DetailViewController classes for you. Expand Resources and notice the template generated a separate nib, DetailView.xib, for the “SplitView”. (See the figure2)



Figure 2: Classes and Resources

Step 4: We need to add images in the Resources folder. Give the name of the images “ipod.jpg”,”mac-mini.jpg”,”macbook.jpg”,”macbook_pro.jpg”.

Step 5: Open the AppDelegate.m file, in the application didFinishLaunchingWithOptions: method allocated RootViewController and DetailViewController with the SplitViewController. So make the following changes in the file:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch

// Add the split view controller’s view to the window and display.

rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];

detailViewController = [[DetailViewController alloc] initWithNibName:@"DetailView" bundle:nil];
rootViewController.detailViewController = detailViewController;

splitViewController = [[UISplitViewController alloc] init];
splitViewController.viewControllers = [NSArray arrayWithObjects:navigationController, detailViewController, nil];
splitViewController.delegate = detailViewController;

[window addSubview:splitViewController.view];
[window makeKeyAndVisible];

return YES;
}

Step 6: We need to add list of data in the Property list for this application. So add first property list in the Resources folder. Once it’s added to the project ,single click .plist its just look like (below figure 3).



Figure 3: pList

Step 7: Open the RootViewController.h file and added NSMutableArray, basically RootViewController handling left view of the SplitView simply it is showing a table view. So make the following changes in the file:

#import
@class DetailViewController;

@interface RootViewController : UITableViewController {
DetailViewController *detailViewController;

NSMutableArray * phone;
}

@property (nonatomic, retain) IBOutlet DetailViewController *detailViewController;
@property (nonatomic, retain) NSMutableArray * phone;

Step 8: Now open the RootViewController.m file and make the following changes in the file:

- (CGSize)contentSizeForViewInPopoverView {
return CGSizeMake(320.0, 600.0);
}
- (void)viewDidLoad {
[super viewDidLoad];
self.phone = [[NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"phone" ofType:@"plist"]] retain];

- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [phone count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"CellIdentifier";

// Dequeue or create a cell of the appropriate type.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryNone;
}

// Configure the cell.
cell.textLabel.text = [self.phone objectAtIndex:indexPath.row];
return cell;
}

- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

/*
When a row is selected, set the detail view controller’s detail item to the item associated with the selected row.
*/
detailViewController.detailItem = [self.phone objectAtIndex:indexPath.row];
}

}

Step 9: In the DetailViewController.h file, we have added UIImageView for displaying image in the right pane and added navigationBar. DetailViewController handle the right pane of the SplitView. So make the following changes in the file:

#import
@interface DetailViewController : UIViewController {

UIPopoverController *popoverController;
UINavigationBar *navigationBar;
id detailItem;
IBOutlet UIImageView * phoneView;
}

@property (nonatomic, retain) IBOutlet UINavigationBar *navigationBar;
@property (nonatomic, retain) id detailItem;
@property (nonatomic, retain) IBOutlet UIImageView * phoneView;

Step 10: Double click the DetailView.xib file and open it to the Interface Builder. First drag the NavigationBar from the library and place it top portion of the view window. Next drag the ImageView from the library and place it to the view window. Now connect the File’s Owner icon to View icon and select the view. Next select the File’s Owner icon and bring up connection inspector connect navigationBar to the NavigationBar (See figure4) and connect the phoneView to the ImageView (See figure5). Now save the DetailView.xib file , close it and go back to the Xcode.



Figure 4: Connection between navigationBar to the NavigationBar



Figure 4: Connection between phoneView to the ImageView

Step 11: Open the DetailView.m file and make the following changes:

- (void)setDetailItem:(id)newDetailItem {
if (detailItem != newDetailItem) {
[detailItem release];
detailItem = [newDetailItem retain];
navigationBar.topItem.title = detailItem;
NSString * phoneimage = [NSString stringWithFormat:@"%@.jpg",detailItem];
[self.phoneView setImage:[UIImage imageNamed:phoneimage]];
}

if (popoverController != nil) {
[popoverController dismissPopoverAnimated:YES];
}
}

- (void)splitViewController: (UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController: (UIPopoverController*)pc {

barButtonItem.title = @"Phone List";
[navigationBar.topItem setLeftBarButtonItem:barButtonItem animated:YES];
self.popoverController = pc;

}

- (void)splitViewController: (UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {

[navigationBar.topItem setLeftBarButtonItem:nil animated:YES];
self.popoverController = nil;

}

Step 12: Now compile and run the application in the Simulator.



Figure 5: In portrait mode



Figure 6: In landscape mode

You can Download SourceCode from here SplitView

Tuesday, February 15, 2011

Multiple row selection and editing in a UITableView

By default, UITableView only supports single-row selection. In this post, I'll show you how to implement multi-row selection, similar to the message edit interface in Mail.

Introduction
The target behavior for this post is an editing mode for a UITableView which allows the selection of multiple rows and presents a button to perform an action on the selection rows.

The following is a screenshot of the sample application running:


When not editing, neither the column of circles and check marks nor the bottom toolbar is visible. When the "Edit" button is clicked (located in place of the cancel button above), the "Edit" button is replaced by the "Cancel" button and the circles and check marks column and the bottom toolbar animate in.

Requirements for the implementation
UITableView does not support multiple selection. We will use the method tableView:didSelectRowAtIndexPath: to detect touches in rows but the selected state will need to be stored separately (we cannot rely on the UITableView's selection).

We will also need a background view for displaying the selection color and a UIImageView for displaying the not-selected/selected indicator. Since the UIImageView will be hidden while not editing and the label for the row needs to move left or right when it is shown or hidden, we will also need to implement some form of layout for the UITableViewCell.

Other required behaviors include switching the "Edit"/"Cancel" buttons between modes, showing/hiding the toolbar at the bottom and tracking the number of selected rows to display in the button in the toolbar.

Implementation
The implementation begins with Apple's default "Navigation-based Application" template.

I then changed the RootViewController to be a subclass of the GenericTableViewController implementation that I presented in my previous Heterogeneous cells in a UITableViewController post. In that post, this class was presented to aid handling of different cell types in one table. I use it again here with only one cell type because in this case, the CellController provides a convenient object in which to store the "selected" state for each row and allows me to keep each file smaller and narrower in focus because it keeps "row" behavior out of the table controller.

Toolbar
The first addition I made was that of the toolbar. This is initially hidden but needs to animate onto the screen when the edit mode is entered.

The toolbar is constructed in the viewDidLoad implementation:

actionToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 416, 320, 44)];
actionButton =
[[[UIBarButtonItem alloc]
initWithTitle:@"No Action"
style:UIBarButtonItemStyleBordered
target:self
action:@selector(noAction:)]
autorelease];
[actionToolbar setItems:[NSArray arrayWithObject:actionButton]];
but cannot be easily added to the view hierarchy at this time. Instead, we wait until the viewDidAppear: method is invoked and add it as a child of the table's parent (the UINavigationController's content frame):

- (void)viewDidAppear:(BOOL)animated
{
[self.view.superview addSubview:actionToolbar];
}
The initial location of the toolbar is below the bottom of the screen, so when editing begins, we need to move it up onto the screen. When editing ends, it is moved back again. This frame animation occurs in the showActionToolbar: method.

Edit Mode
Standard edit modes for UITableViews are started by calling setEditing:animated: on the UITableView. We are not going to use any of the standard UITableViewCellEditingStyles, so invoking this method is not strictly required but it will propagate a notification to the UITableViewCells and allow us to query the state at a later time so we will use it anyway.

The edit: and cancel: methods switch us into and out of "Edit" mode respectively.

- (void)edit:(id)sender
{
[self showActionToolbar:YES];

UIBarButtonItem *cancelButton =
[[[UIBarButtonItem alloc]
initWithTitle:@"Cancel"
style:UIBarButtonItemStyleDone
target:self
action:@selector(cancel:)]
autorelease];
[self.navigationItem setRightBarButtonItem:cancelButton animated:NO];
[self updateSelectionCount];

[self.tableView setEditing:YES animated:YES];
}

- (void)cancel:(id)sender
{
[self showActionToolbar:NO];

UIBarButtonItem *editButton =
[[[UIBarButtonItem alloc]
initWithTitle:@"Edit"
style:UIBarButtonItemStylePlain
target:self
action:@selector(edit:)]
autorelease];
[self.navigationItem setRightBarButtonItem:editButton animated:NO];

NSInteger row = 0;
for (MultiSelectCellController *cellController in
[tableGroups objectAtIndex:0])
{
[cellController clearSelectionForTableView:self.tableView
indexPath:[NSIndexPath indexPathForRow:row inSection:0]];
row++;
}

[self.tableView setEditing:NO animated:YES];
}
Here you can see the "Edit"/"Cancel" buttons being swapped, the toolbar being shown/hidden and setEditing:animated: being invoked. I also implement tableView:canEditRowAtIndexPath: to always return yes, since all rows may be edited in this table.

Showing/hiding the check mark column
When setEditing:animated: is invoked on the table, the table in turn invokes the setEditing:animated: on all visible UITableViewCells, allowing each row to update for editing.

In response to this, we need to show/hide the check mark column. We handle this in a UITableViewCell subclass where the setEditing:animated: is implemented to call setNeedsLayout and the layoutSubviews method is overridden to handle different layouts for the "Edit" an "Not Editing" modes.

When editing, the cell's contentView is shifted to the right, otherwise it is layed out flush against the left side. This is all we'll need to display the extra column because the check mark column is always present in the cell. Outside of editing mode, it is layed out off the left of screen (so you can't see it). When the contentView is shifted right by 35 pixels during editing, the check mark column (which is located at the contentView's origin minus 35 pixels horizontally) becomes visible.

- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[self setNeedsLayout];
}

- (void)layoutSubviews
{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];

[super layoutSubviews];

if (((UITableView *)self.superview).isEditing)
{
CGRect contentFrame = self.contentView.frame;
contentFrame.origin.x = EDITING_HORIZONTAL_OFFSET;
self.contentView.frame = contentFrame;
}
else
{
CGRect contentFrame = self.contentView.frame;
contentFrame.origin.x = 0;
self.contentView.frame = contentFrame;
}

[UIView commitAnimations];
}
The setEditing:animated: implementation ensures that re-layout occurs every time edit mode is entered/exited.

Notice that no custom drawing happens here in the UITableViewCell. I've seen many people override UITableViewCell for custom drawing but I don't think it's a good idea. The UITableViewCell is really just a layout container and that should be the only way you use it. Custom drawing should go in the backgroundView, selectionView or contentView that the UITableViewCell contains.

Drawing the cell
I use a UILabel for the text rather than setting the text property of the cell because it makes it easier to get a transparent background for the text (which I'll need to see the blue selection color).

I set cell.selectionStyle = UITableViewCellSelectionStyleNone because I don't want to use the standard selection view at all (it is limited to single rows). Instead, I achieve a selection color by creating a backgroundView for the cell and setting its background color to white or pale blue as appropriate.

The selection indicator is just a UIImageView. As previously indicated, it is layed out 35 pixels left of the contentView which places it offscreen. When the contentView is shifted right during editing, it will become visible.

The only other important behavior is that the CellController must invoke updateSelectionCount on the RootViewController when selected/deselected so that the selection count can be updated when the selection changes. I implement this in a lazy fashion by recounting all selected rows — you should probably implement this in a more efficient fashion.

Conclusion
You can download the complete MultiRowSelect Xcode 3.1 project (40kB).
The final result is a few hundred lines of code. This is not a giant mountain of code by any means but still a considerable volume given how simple "multi-row selection" might seem as a description. I think this serves to show that user-interface implementations can be very time consuming when the desired functionality is not provided by the default libraries.

None of the code is particularly complex but it still involves a lot of coordination between the table, table controller and cell so I hope that this sample implementation simplifies the task for anyone else who needs to implement it in the future.