Translate

Wednesday, 2 July 2014

web service in iOS

Many apps use data pulled from the web, so developers need to know how to get and manipulate that data. 
Okay, we are going to begin to get into web services in an iOS environment. Most apps now use data from the web. Whether that data comes from a private (business back end) or public (weather services) source, most apps use it.
So for this app we will take baby steps towards reading data off the web and parsing it (a fancy name for converting it to our iOS side) into usable iOS data for our app. Then we will worry about what to do with our data inside our app.

Getting started

We have already experienced this with our Twitter app, but we will make it a bit more useful this time - as well as entertaining. With great power comes great responsibility, so this means you will have to delve into a bit of server programming. It will not be too complicated I promise, but it will be very enlightening. Thus one of the requisites for this tutorial is that you have access to some public web server (or you could use a webserver for mac such as MAMP, which you can download.
MOCK DATA - Example Free Web Service Flickr
I have a website setup where you can read data for testing purposes. In your web browser go to:
This displays a json response. It's a little atypical because it has a leading string in there, but the rest (beginning with the first "(") is typical. Ok so let's try to fetch this data from within our app. Create a new Single View Project with ARC and Storyboards and call it TRWebService.
Select the Class Files for ViewController and click Delete. When you are prompted to answer this question click on Move To Trash (Figure A).

Figure A


a_mvalenzeula_WebServ1.png

Go ahead and click on Move To Trash.
When you select Remove References you are basically just removing the existing XCode references to those files, but the files are still there. So if you think you may need files later, just maybe not for this particular project, then Remove References and you can later move the actual files from the TRWebService XCode project folder to some other folder. If you are positive you will never use a file again, go ahead and Move To Trash.
Now let's add a New File and make it a tableview controller (Figure B)

Figure B


b_mvalenzeula_WebServ1.png

I did this for two reasons; first, it shows how to remove files and add files. Second, because this way we have XCode prepare a UITableViewController for us which gives us those boilerplate methods that come with all UITVCs instead of having to remember what they are called or how they are coded. As to why I chose UITVC, I guess I just like scrolling cells!
UITVCs are nice because they come with some methods, which we can use to explore the viewcontroller lifecycle. Let's look at some of these methods now:
-initWithStyle: This method is used to add a particular styling to tableviews. If we build and run the app now we get this CRASH hahaha! I forgot to replace the scene in storyboard. But I decided to leave the crash in the tutorial; it's a good way to get used to them, because they are great for learning. Let's analyze the error shall we:
'NSInternalInconsistencyException', reason: '-[UITableViewController loadView] loaded the "2-view-3" nib but didn't get a UITableView.'
XCode is telling us that UITableViewController's loadView method tried to load a tableview from the nib, but didn't get one. Of course, it didn't. There isn't one! Our app came with a storyboard which included a UIViewController scene, not a UITVC one. So let's erase the UIViewController scene from storyboard. Select the scene until there is a blue rectangle around it and simply delete it. Now drag a UITVC from the Object Library onto the storyboard. Don't forget to set its Class Type in the Identity Inspector to ViewController. Now let's run the app again. Great! Now we get Figure C.

Figure C


c_mvalenzeula_WebServ1.png

So in this method you can modify some style settings. It gets called only once, at the very beginning of the viewcontroller's life. Other such methods which get called only once and at the very beginning are: initWithCoder, initWithFrame and viewDidLoad.
  • -viewDidLoad is a method that you will see quite a lot because it is used as a starting point for many other tasks. After all, once the view has loaded, the user can begin to see things on the screen.
  • -viewWillAppear, as well as its other closely related cousins, viewDidAppear, viewWillDisappear are methods which are called every time the view will do this or that. The difference is that views can appear many times but they are only loaded once. So for example, if you want the view to be refreshed each time the user loads an app running in the background, or switches tabs within a UITabBarController etc., you want to use the methods call every time the view appears. If you refresh data inside viewDidLoad, of the other previous methods such as initWithStyle/Coder/Frame/NibName, the data will only be refreshed once!
  • -nOSIT, nORIS, cFRAIP and dSRAIP are tableview methods and I use those abbreviations to refer to them. They are not standard acronyms so be prepared for people to go "HUH!?". numberOfSectionsInTableView, numberOfRowsInSection, cellForRowAtIndexPath and didSelectRowAtIndexPath are for the most part, required methods in a tableview. nORIS and nOSIT get called only once but cFRAIP gets called many times, each time new cells appear in view for whatever reason, scrolling, loading of the view, deletion, editing etc.
Okay, enough about boilerplate methods of UITVC. Other VCs have different methods based upon what they do and which protocols they conform to.

Get some data

So now we want to get some data off the web before we put it into our iOS app. We do this in three steps; create a URL, create a URLRequest and finally make a URLConnection. Since these are all Mac methods they are called NSURL, NSURLRequest and NSURLConnection.  Our code will look something like this:
NSURL *myURL = [NSURL URLWithString:@"http://www.flickr.com/services/rest/?method=flickr.test.echo&format=json&api_key=8038f7f7d7151ccbf6df2aa10b1b35ae&nojsoncallback=1"];

 

  NSURLRequest *myRequest = [NSURLRequest requestWithURL:myURL];

 

  NSURLConnection *myConnection = [NSURLConnection connectionWithRequest:myRequest delegate:self];
Now the way this works is that it creates a connection which fetches and returns a response and some data if the response is positive. NSURLConnection sets the current view controller as its delegate so this viewcontroller must implement the delegate methods that will receive the response and the data.
I just want to review the concept about not blocking the main thread. This is so important because what happens is that we want to have all of our data fetched by the time the user interacts with out viewcontrollers. So you would be tempted to go as far back as the AppDelegate's applicationDidFinishLaunchingWithOptions method to put this code and the respective delegate methods. We can do this for this example because the response and data is returned so quickly that it doesn't pose a problem. But what if the Internet connection was missing or slow?
So let's move it to our ViewController. Go ahead and make your viewDidLoad method look like this:
- (void)viewDidLoad{

  [super viewDidLoad];

  NSURL *myURL = [NSURL URLWithString:@"http://www.flickr.com/services/rest/?method=flickr.test.echo&format=json&api_key=8038f7f7d7151ccbf6df2aa10b1b35ae&nojsoncallback=1"];

 

  NSURLRequest *myRequest = [NSURLRequest requestWithURL:myURL];

  NSURLConnection *myConnection = [NSURLConnection connectionWithRequest:myRequest delegate:self];

}

And add these methods below:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{

 

  NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response;

  int errorCode = httpResponse.statusCode;

  NSString *fileMIMEType = [[httpResponse MIMEType] lowercaseString];

  NSLog(@"response is %d, %@", errorCode, fileMIMEType);

}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

  NSLog(@"data is %@", data);

}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {

 

  // inform the user

  NSLog(@"Connection failed! Error - %@ %@",

  [error localizedDescription],

  [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {

  // do something with the data

  // receivedData is declared as a method instance elsewhere

  NSLog(@"Succeeded!");

 

}
Now don't flip just yet. They look intimidating but they're quite easy to understand.
If we receive a response then the method didReceiveResponse is called and we can do something with that response. Have you ever seen the infamous HTTP 404 error codes when loading a page that no longer exists? Well, HTTP is a protocol much like any other and as such, when a resource is not found, it displays a standardized response, called response code, which is #404. We can get the code responded by the Flickr server as well as the return file type and log them in our console.
If the response received is good, which by the way is known as an HTTP 200 code, the server begins to send data. That data is received in the didReceiveData method. If there is an error in the connection then the didFailWithError method is called. You can test this one out by turning off your Internet connection and running the app. You will see the actual error returned by the Flickr server.
Finally, once all the data from the connection has been received, the connectionDidFinishLoading method is called and we can manipulate our data. So before we get to playing with data, let's go ahead and run the app. Look in the console and you should get something like this:
2013-04-10 09:56:22.336 TRWebService[1032:c07] response is 200, text/javascript

2013-04-10 09:56:22.336 TRWebService[1032:c07] data is <6a736f6e 466c6963 6b724170 69287b22 6d657468 6f64223a 7b225f63 6f6e7465 6e74223a 22666c69 636b722e 74657374 2e656368 6f227d2c 2022666f 726d6174 223a7b22 5f636f6e 74656e74 223a226a 736f6e22 7d2c2022 6170695f 6b657922 3a7b225f 636f6e74 656e7422 3a223830 33386637 66376437 31353163 63626636 64663261 61313062 31623335 6165227d 2c202273 74617422 3a226f6b 227d29>

2013-04-10 09:56:22.337 TRWebService[1032:c07] Succeeded!
As you can see there is our response code and our file type. Now let's try to take this data and put it into an iOS object we can use. We know the type returned is text, so we might be tempted to put this data into a string. To do this we will change the didReceiveData method to this:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

  NSLog(@"data is %@", data);

  NSString *myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

  NSLog(@"string is %@", myString);

}
Now run the app and you will get the same result you got in your web browser, in your console!
string is {"method":{"_content":"flickr.test.echo"}, "format":{"_content":"json"}, "api_key":{"_content":"8038f7f7d7151ccbf6df2aa10b1b35ae"}, "nojsoncallback":{"_content":"1"}, "stat":"ok"}
Great! We are one step closer to getting our data. Now we just need to put it into an iOS object so we can pass it around and do whatever we want with it. So modify your method to this:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

  NSLog(@"data is %@", data);

  NSString *myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

  NSLog(@"string is %@", myString);

  NSError *e = nil;

  NSDictionary *flickrDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&e];

  NSLog(@"dictionary is %@", flickrDictionary);

}
And now you should get this as well:
dictionary is {

  "api_key" =  {

  "_content" = 8038f7f7d7151ccbf6df2aa10b1b35ae;

  };

  format =  {

  "_content" = json;

  };

  method =  {

  "_content" = "flickr.test.echo";

  };

  nojsoncallback =  {

  "_content" = 1;

  };

   stat = ok;

}
This means it is now an NSDictionary and we can use the data in our app. Not so painful after all!
Ok so let's first make our flickrDictionary into an ivar instead of a local variable of the didReceiveData method. Add it to the interface in the ViewController.m file, up top. Go ahead and add a mutable array as well so we can parse the dictionary and put the objects into the array for use in the tableview.
So now the top of your ViewController.m file will look like this:
#import "ViewController.h"

@interface ViewController () {

  NSDictionary *flickrDictionary;

  NSMutableArray *cFRAIPArray;}

@end

@implementation ViewController…
And you will change your connection:didReceiveData line to this:
  flickrDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&e];
Now look carefully at what we have. It's a dictionary of dictionaries. The first dictionary entry is "api_key", whose value is set to a "_content" dictionary. The second entry is "format" whose key is also a "_content" dictionary, so on and so forth. What we want to do is take the _content value of each and put it into our array so we can use the array to populate our cells. Since we want to parse the dictionary only once it is fully populated from the web fetch, let's change our connectionDidFinishLoading method to this:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {

  cFRAIPArray = [[NSMutableArray alloc] initWithCapacity:5];

  for (NSString *key in flickrDictionary) {

  NSLog(@"the key is %@", flickrDictionary[key]);

  id object = flickrDictionary[key]; // this will be apikey,format,method etc...

  if ([object isKindOfClass:[NSDictionary class]]) { //first 4 are arrays-crsh-dicts

  NSLog(@"object valueForKey is %@", [object valueForKey:@"_content"]);

  [cFRAIPArray addObject:[object valueForKey:@"_content"]];

 

  } else {

  [cFRAIPArray addObject:flickrDictionary[key]];

  }

  }

  NSLog(@"cfraiparray %@", cFRAIPArray);

  [self.tableView reloadData];

}
So we declare that our cFRAIPArray will contain five objects. We then loop through all its keys and put them into an id object. Now we have to test if those objects are dictionaries (as the first four are) or else, like the last entry, which is just a string. In the case the object IS an NSDictionary grab the value for the key called "_content" and add it to our cFRAIPArray. Finally we reload the tableview.
Pay close attention to the subtle difference between each if/else code branch. In the first, we test if flickrDictionary's key is a dictionary, and if it is, we put THAT key into a new dictionary called "object". We then get the value for the "_content" key of the OBJECT dictionary (NOT of the flickrDictionary) and add it to the cFRAIPArray. Then in the last entry, we simply take the flickDictionary key and add it to the cFRAIPArray.
Then in our cFRAIP method we simply say:
cell.textLabel.text = [cFRAIPArray objectAtIndex:indexPath.row];
Now Build & Run and you have a perfectly populated tableview. (Figure D)

Figure D


d_mvalenzeula_WebServ1.png

Monday, 23 June 2014

Custom Alert views

Hi All today i will be showing how to create custom alert views in iOS.

OVERVIEW

There are certain situations when blue Alert View just doesn't look good with your app - either because of color or because of style, so that you need to customize the way it looks. The first idea that comes to mind would be to subclass UIAlertView, but class reference states following:
The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.
This tutorial will show how to create your own CustomAlert class which inherits from UIView and provides functionality similar to UIAlertView class by implementing the following features:

  1. Scaling background picture depending on message size
  2. Managing message text (by decreasing font size and adding scroll)
  3. Informing delegate about button pressed
This tutorial uses graphics which is very similar to native iOS alert view, but by replacing background and button pictures with your own you can achieve a very different look.

Here's how final result will look like:


1. SETTING UP THE PROJECT 

Open XCode and create new project using Single View Application template: 


Name the project, select "iPhone" option from "Devices" menu, make sure that "Use Automatic Reference Counting" is checked, press "Next" one more time and save your project.

Now create new file (shortcut CMD+N) which inherits from UIView and name it CustomAlert. 
By now your project hierarchy should look like this:

2. ADDING GRAPHICS

In order to create custom alert, you will need to include the following files to your project:

1. Alert background picture:

alert_bg.png

2. Cancel button picture:

cancel_btn.png

3. Other button picture:

other_btn.png

Add those pictures to your project as well (you can replace them with the ones you need), but please note that @2x version of the files should be added for Retina display support.

3. ADDING FUNCTIONALITY 

Setting up CustomAlert.h

Open CustomAlert.h file and do the following:
  • Add property of type id named delegate
  • Add variable of type UIView named AlertView
By now it should look like this:

#import <UIKit/UIKit.h>

@interface CustomAlert : UIView
{
    id delegate;
    UIView *AlertView;
}
@property id delegate;

@end
Now let's think about class methods - basically alert should be initialized and shown. During initialization alert should be supplied with title, subtitle, message, button names and delegate. Add the following code to header file to represent this functionality:

- (id)initWithTitle:(NSString *)title message:(NSString *)message delegate:(id)AlertDelegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitle:(NSString *)otherButtonTitle;
- (void)showInView:(UIView*)view;
After alert was initialized and shown, user is going to press some button, and that's when delegate becomes useful. Add CustomAlert delegate as follows:

@protocol CustomAlertDelegate
- (void)customAlertView:(CustomAlert*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;
@end

Setting up CustomAlert.m

Open CustomAlert.m file and add the following line before init method:

@synthesize delegate;

Now let's modify initialization method so that it will look the same was as in header. In order to do so, replace the following line:

- (id)initWithFrame:(CGRect)frame

with this one:

- (id)initWithTitle:(NSString *)title message:(NSString *)message delegate:(id)AlertDelegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitle:(NSString *)otherButtonTitle

You've probably noticed that old init message was taking frame as parameter, and since new method does not have it, we have nothing to pass to superview. The reason why new init message does not take frame as parameter is very simple - alert should take the whole screen, so frame would be equal to screen size. Let's write this in code (insert it before self = [super initWithFrame:frame]; line):

CGRect frame;
if ([[UIApplication sharedApplication] statusBarOrientation] == UIDeviceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIDeviceOrientationLandscapeRight)
    frame = CGRectMake(0.0, 0.0, [[UIScreen mainScreen] bounds].size.height, [[UIScreen mainScreen]bounds].size.width);
else
    frame = CGRectMake(0.0, 0.0, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen]bounds].size.height);

This way depending on screen orientation, CustomAlert view will have frame equal to screen size. But what happens when user rotates the phone? That's when autoresizingMask is used (add the following code after "if (self) {" line):

self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;

Now let's focus on how alert looks on iPhone - it definitely has black opaque background, but setting background color doesn't do the whole trick. As you might notice, blue alert is transparent itself as well, which is achieved by adding transparency to the whole view in addition to setting black opaque background:


self.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.6];
self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;


Next step would be proper scaling alert_bg.png picture. Basically you wouldn't want to stretch 25 pixels at left, right and bottom sides due to rounded corners, but at the top picture also has lighter glow which would be around 40 px high. To achieve that, resizableImageWithCapInsets: function is available for iOS 5.0 and up, but if you want to support older iOS versions, your code should look like this (detailshere):

UIImage *AlertBg = [UIImage imageNamed:@"alert_bg.png"];

if ([AlertBg respondsToSelector:@selector(resizableImageWithCapInsets:)])
    AlertBg = [[UIImage imageNamed: @"alert_bg.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(40.0,25.0, 25.0, 25.0)];
else
    AlertBg = [[UIImage imageNamed: @"alert_bg.png"] stretchableImageWithLeftCapWidth: 25 topCapHeight:40];



Now all we need to do is to add labels for title and message, and buttons. The following code checks all possible combinations (basically you can create alert with no title or no buttons, etc).

There's only one problem left - what to do if message text is really long? First, let's define maximum possible alert height to 300 px (so that it will still fit screen in landscape mode) by adding the following line at the top of file:

#define MAX_ALERT_HEIGHT 300.0

Scaling message would be implemented like this - first we'll try to gradually decrease font size, but we can't do that indefinitely since message might be impossible to read. So to make sure the whole message can be read, we will add scroll view. This way if message fits - scroll is disabled, but if it doesn't fit - user can use scroll to see the whole message.

The here's the final code:

UIImage *CancelBtnImg = [UIImage imageNamed:@"cancel_btn.png"];
UIImage *OtherBtnImg = [UIImage imageNamed:@"other_btn.png"];

UIImageView *AlertImgView = [[UIImageView alloc] initWithImage:AlertBg];

float alert_width = AlertImgView.frame.size.width;
float alert_height = 5.0;

//add text
UILabel *TitleLbl;
UIScrollView *MsgScrollView;

if (title)
{
    TitleLbl = [[UILabel alloc] initWithFrame:CGRectMake(10.0, alert_height, alert_width-20.0, 30.0)];
    TitleLbl.adjustsFontSizeToFitWidth = YES;
    TitleLbl.font = [UIFont boldSystemFontOfSize:18.0];
    TitleLbl.textAlignment = UITextAlignmentCenter;
    TitleLbl.minimumFontSize = 12.0;
    TitleLbl.backgroundColor = [UIColor clearColor];
    TitleLbl.textColor = [UIColor whiteColor];
    TitleLbl.text = title;
    
    alert_height += TitleLbl.frame.size.height + 5.0;
}
else
    alert_height += 15.0;

if (message)
{
    float max_msg_height = MAX_ALERT_HEIGHT - alert_height - ((cancelButtonTitle || otherButtonTitle)?(CancelBtnImg.size.height+30.0):30.0);
    
    UILabel *MessageLbl = [[UILabel alloc] initWithFrame:CGRectMake(10.0, 0.0, alert_width-40.0, 0.0)];
    MessageLbl.numberOfLines = 0;
    MessageLbl.font = [UIFont systemFontOfSize:16.0];
    MessageLbl.textAlignment = UITextAlignmentCenter;
    MessageLbl.backgroundColor = [UIColor clearColor];
    MessageLbl.textColor = [UIColor whiteColor];
    MessageLbl.text = message;
    
    [MessageLbl sizeToFit];
    MessageLbl.frame = CGRectMake(10.0, 0.0, alert_width-40.0, MessageLbl.frame.size.height);
    
    while (MessageLbl.frame.size.height>max_msg_height && MessageLbl.font.pointSize>12) {
        MessageLbl.font = [UIFont systemFontOfSize:MessageLbl.font.pointSize-1];
        [MessageLbl sizeToFit];
        MessageLbl.frame = CGRectMake(10.0, 0.0, alert_width-40.0, MessageLbl.frame.size.height);
    }
    
    MsgScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(10.0, alert_height, alert_width-20.0, (MessageLbl.frame.size.height>max_msg_height)?max_msg_height:MessageLbl.frame.size.height)];
    MsgScrollView.contentSize = MessageLbl.frame.size;
    [MsgScrollView addSubview:MessageLbl];
    
    alert_height += MsgScrollView.frame.size.height + 15.0;
}
else
    alert_height += 15.0;

//add buttons
UIButton *CancelBtn;
UIButton *OtherBtn;

if (cancelButtonTitle && otherButtonTitle)
{
    float x_displ = (int)((alert_width-CancelBtnImg.size.width*2)/3.0);
    CancelBtn = [[UIButton alloc] initWithFrame:CGRectMake(x_displ, alert_height, CancelBtnImg.size.width, CancelBtnImg.size.height)];
    [CancelBtn setTag:1000];
    [CancelBtn setTitle:cancelButtonTitle forState:UIControlStateNormal];
    [CancelBtn.titleLabel setFont:[UIFont boldSystemFontOfSize:18.0]];
    [CancelBtn setBackgroundImage:CancelBtnImg forState:UIControlStateNormal];
    [CancelBtn addTarget:self action:@selector(onBtnPressed:) forControlEvents:UIControlEventTouchUpInside];
    
    OtherBtn = [[UIButton alloc] initWithFrame:CGRectMake(x_displ*2+CancelBtnImg.size.width, alert_height, OtherBtnImg.size.width, OtherBtnImg.size.height)];
    [OtherBtn setTag:1001];
    [OtherBtn setTitle:otherButtonTitle forState:UIControlStateNormal];
    [OtherBtn.titleLabel setFont:[UIFont boldSystemFontOfSize:18.0]];
    [OtherBtn setBackgroundImage:OtherBtnImg forState:UIControlStateNormal];
    [OtherBtn addTarget:self action:@selector(onBtnPressed:) forControlEvents:UIControlEventTouchUpInside];
    
    alert_height += CancelBtn.frame.size.height + 15.0;
}
else if (cancelButtonTitle)
{
    CancelBtn = [[UIButton alloc] initWithFrame:CGRectMake((int)((alert_width-CancelBtnImg.size.width)/2.0), alert_height, CancelBtnImg.size.width, CancelBtnImg.size.height)];
    [CancelBtn setTag:1000];
    [CancelBtn setTitle:cancelButtonTitle forState:UIControlStateNormal];
    [CancelBtn.titleLabel setFont:[UIFont boldSystemFontOfSize:18.0]];
    [CancelBtn setBackgroundImage:CancelBtnImg forState:UIControlStateNormal];
    [CancelBtn addTarget:self action:@selector(onBtnPressed:) forControlEvents:UIControlEventTouchUpInside];
    
    alert_height += CancelBtn.frame.size.height + 15.0;
}
else if (otherButtonTitle)
{
    OtherBtn = [[UIButton alloc] initWithFrame:CGRectMake((int)((alert_width-OtherBtnImg.size.width)/2.0), alert_height, OtherBtnImg.size.width, OtherBtnImg.size.height)];
    [OtherBtn setTag:1001];
    [OtherBtn setTitle:otherButtonTitle forState:UIControlStateNormal];
    [OtherBtn.titleLabel setFont:[UIFont boldSystemFontOfSize:18.0]];
    [OtherBtn setBackgroundImage:OtherBtnImg forState:UIControlStateNormal];
    [OtherBtn addTarget:self action:@selector(onBtnPressed:) forControlEvents:UIControlEventTouchUpInside];
    
    alert_height += OtherBtn.frame.size.height + 15.0;
}
else
    alert_height += 15.0;

//add background

AlertView = [[UIView alloc] initWithFrame:CGRectMake((int)((self.frame.size.width-alert_width)/2.0), (int)((self.frame.size.height-alert_height)/2.0), alert_width, alert_height)];
AlertView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | 
UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
AlertImgView.frame = AlertView.bounds;
[AlertView addSubview:AlertImgView];

[self addSubview:AlertView];

if (TitleLbl)
    [AlertView addSubview:TitleLbl];

if (MsgScrollView)
    [AlertView addSubview:MsgScrollView];

if (CancelBtn)
    [AlertView addSubview:CancelBtn];

if (OtherBtn)
    [AlertView addSubview:OtherBtn];

Next step would be to add onBtnPressed: method (all we need to do is to inform delegate which button was pressed):

- (void)onBtnPressed:(id)sender
{
    UIButton *button = (UIButton *)sender;
    
    int button_index = button.tag-1000;
    
    if ([delegate respondsToSelector:@selector(customAlertView:clickedButtonAtIndex:)])
        [delegate customAlertView:self clickedButtonAtIndex:button_index];
    
    [self animateHide];
}

Now let's implement showInView: method:

- (void)showInView:(UIView*)view
{
    if ([view isKindOfClass:[UIView class]])
    {
        [view addSubview:self];
        [self animateShow];
    }
}

And the last step would be implementing animation, which requires adding QuartzCore.framework to your project and adding #import<QuartzCore/QuartzCore.h> line to CustomAlert.h file. Here are animation functions:


- (void)animateShow
{
    CAKeyframeAnimation *animation = [CAKeyframeAnimation
                                      animationWithKeyPath:@"transform"];
    
    CATransform3D scale1 = CATransform3DMakeScale(0.5, 0.5, 1);
    CATransform3D scale2 = CATransform3DMakeScale(1.2, 1.2, 1);
    CATransform3D scale3 = CATransform3DMakeScale(0.9, 0.9, 1);
    CATransform3D scale4 = CATransform3DMakeScale(1.0, 1.0, 1);
    
    NSArray *frameValues = [NSArray arrayWithObjects:
                            [NSValue valueWithCATransform3D:scale1],
                            [NSValue valueWithCATransform3D:scale2],
                            [NSValue valueWithCATransform3D:scale3],
                            [NSValue valueWithCATransform3D:scale4],
                            nil];
    [animation setValues:frameValues];
    
    NSArray *frameTimes = [NSArray arrayWithObjects:
                           [NSNumber numberWithFloat:0.0],
                           [NSNumber numberWithFloat:0.5],
                           [NSNumber numberWithFloat:0.9],
                           [NSNumber numberWithFloat:1.0],
                           nil];
    [animation setKeyTimes:frameTimes];
    
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    animation.duration = 0.2;
    
    [AlertView.layer addAnimation:animation forKey:@"show"];
}

- (void)animateHide
{
    CAKeyframeAnimation *animation = [CAKeyframeAnimation
                                      animationWithKeyPath:@"transform"];
    
    CATransform3D scale1 = CATransform3DMakeScale(1.0, 1.0, 1);
    CATransform3D scale2 = CATransform3DMakeScale(0.5, 0.5, 1);
    CATransform3D scale3 = CATransform3DMakeScale(0.0, 0.0, 1);
    
    NSArray *frameValues = [NSArray arrayWithObjects:
                            [NSValue valueWithCATransform3D:scale1],
                            [NSValue valueWithCATransform3D:scale2],
                            [NSValue valueWithCATransform3D:scale3],
                            nil];
    [animation setValues:frameValues];
    
    NSArray *frameTimes = [NSArray arrayWithObjects:
                           [NSNumber numberWithFloat:0.0],
                           [NSNumber numberWithFloat:0.5],
                           [NSNumber numberWithFloat:0.9],
                           nil];
    [animation setKeyTimes:frameTimes];
    
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    animation.duration = 0.1;
    
    [AlertView.layer addAnimation:animation forKey:@"hide"];
    
    [self performSelector:@selector(removeFromSuperview) withObject:self afterDelay:0.105];
}


By this point you should be able successfully compile project, but there's no visible result yet. Let's add a button to view controller which would display alert when clicked.

Setting up ViewController.h

Basically we just need to import CustomAlert.h and set view controller to be its delegate. Here's how the code should look like:

#import <UIKit/UIKit.h>
#import "CustomAlert.h"

@interface ViewController : UIViewController <CustomAlertDelegate>

@end

Setting up ViewController.m

Let's add button by pasting the following code in viewDidLoad function (you can also use interface builder instead of manual code):

UIButton *AlertBtn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
AlertBtn.frame = CGRectMake((int)((self.view.frame.size.width-200.0)/2.0), 120.0, 200.0, 50.0);
[AlertBtn setTitle:@"Show Alert" forState:UIControlStateNormal];
[AlertBtn addTarget:self action:@selector(onAlertBtnPressed)forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:AlertBtn];
AlertBtn.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin |UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;

Now let's add alert in onAlertBtnPressed implementation:

- (void)onAlertBtnPressed
{
    CustomAlert *alert = [[CustomAlert alloc] initWithTitle:@"Warning" message:@"Set background color:"delegate:self cancelButtonTitle:@"Gray" otherButtonTitle:@"White"];
    [alert showInView:self.view];
}

And the final step would be implementing delegate method:

- (void)customAlertView:(CustomAlert*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 0)
    {
        //Gray
        self.view.backgroundColor = [UIColor lightGrayColor];
    }
    else if (buttonIndex == 1)
    {
        //White
        self.view.backgroundColor = [UIColor whiteColor];
    }
}

4. RUNNING THE PROJECT

General case

Compile and run the app, alert view should look like this:


Alert with no buttons

Modify onAlertBtnPressed function the following way:

- (void)onAlertBtnPressed
{
    CustomAlert *alert = [[CustomAlert alloc] initWithTitle:@"Warning" message:@"This is message with no buttons" delegate:self cancelButtonTitle:Nil otherButtonTitle:Nil];
    [alert showInView:self.view];
}

Now alert looks like this:

Please note that in this case you should handle removing alert view manually

5. CONCLUSIONS

By replacing graphics and styling text you can achieve pretty much any look that would look good with your app style.