Category Archives: ios

iOS: Essential Workflow in Objective-C using Delegate and Protocol

Using Delegate and Protocol is an essential design pattern in iOS, used to pass data between View Controllers and orchestrate workflows in iOS. For instance, when calling a URL Connection module, the input variables to make a JSON REST request and the output response data are passed by properties using a callback method defined by the URL Connection module’s protocol. In order to know which class to callback, the calling class sets itself as the delegate property of the URL Connection module, and is then called back via a method it must implement, following the Protocol’s signature.

The basic design consists of a calling class A.class and a called class B.class:
1. Define a Protocol in B with a callback method, e.g. named -(void)returnFromB:(B *)controller.
2. Define a delegate property in B, e.g. @property id returnFromBDelegate.
3. A must implement the callback method that Protocol B prescribes, so add the <BProtocol> and a method -(void)returnFromB:(B *)controller.
4. A calls a workers method in B, and sets itself as the delegate of B.
5. When workers method is done, B calls A back by calling the callback method on its delegate, which is A.


// A.h
#import <UIKit/UIKit.h>
#import "B.h"
@interface A : UIViewController <BProtocol>
@end



// A.m
#import "B.h"
@interface A()
@end
@implementation A
// call B programmatically or use a segue
-(void)callB
{
UIStoryboard *BStoryBoard = [UIStoryboard storyboardWithName:@"BStoryboard" bundle:[NSBundle mainBundle]];
B *b = [BStoryBoard instantiateInitialViewController];
b.returnFromBDelegate = self;
[self presentViewController:b animated:YES completion:^{}];
}
// callback method for B as prescribed by BProtocol
- (void)returnFromB:(B *)controller
{
// for instance dismiss the B controller scene here
[controller dismissViewControllerAnimated:YES completion:^{}];
}
@end



// B.h
#import <UIKit/UIKit.h>
@class B;
@protocol BProtocol
- (void)returnFromB:(B *)controller;
@end
@interface B : UIViewController
@property id returnFromBDelegate;
- (IBAction)backButtonPressed:(id)sender;
@end


// B.m
@interface B ()
@end
@implementation B
@synthesize returnFromBDelegate;
-(IBAction)backButtonPressed:(id)sender
{
[self.returnFromBDelegate returnFromB:self];
}
@end

iOS: Inspect HTTP traffic on Mac OS X

On Windows, Fiddler is a popular tool to inspect HTTP traffic. On Mac and XCode, you can inspect the content of HTTP traffic using the Open Web Application Security Project (OWASP) WebScarab tool.

Installation:
Download and install WebScarab by running the maven or ant build install file. This should produce a webscarab.jar file in the root directory. To run it:

local$ java -jar webscarab.jar

On your Mac go to > System Settings > Network > and select your internet connection, then go to the Advanced… settings. Click the Proxies tab and select ‘Web Proxy (HTTP)’. For Web Proxy Server enter ‘127.0.0.1’ on Port 8008, which is the port WebScarab by default is configured to listen. You can add addresses to bypass the proxy, as many system requests will run on your machine: e.g. *.local, metrics.apple.com, mybrowserbar.com, pixel.adsafeprotected.com, www.gstatic.com, clients2.google.com.

In the WebScarab Proxy tab, go to the Listeners section and make sure that a listener has been added for Address 127.0.0.1 and Port 8008.

In WebScarab go back to the Summary tab to start inspecting HTTP traffic requests and responses. If you double click a single request in the bottom half of your Summary window, a new window will open, with request and response message respectively on top and below. Toggle between Parsed and Raw to see the messages in table or text form.

iOS: UserDefaults and Application Settings

NSUserDefaults are by default restricted to the application’s domain and are stored in the file $HOME/Library/Preferences/<ApplicationBundleIdentifer>.plist. You access the user defaults in code. To add user defaults to the Settings.app in iOS, you must wrap the user defaults in a Settings Bundle.

1. NSUserDefaults stores the user settings in your app. Code or a custom UI without a Settings Bundle is used for frequently changing settings.
2. The Settings Bundle displays NSUserDefaults in the Settings app and is used for infrequently changing settings.

Add a Seetings Bundle by going to File > New > File and browse to the Resource > Settings file.

NewSettings1

To create the following Settings

SettingsApp

change the Settings Bundle you added with the following configuration

SettingsConfig

Read or create user defaults early in your application.


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// to add a user default programmatically
NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:@"test" forKey:@"environment"];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults registerDefaults:appDefaults];
}

Then where you need your user default, read it as follows


NSString *username = [[NSUserDefaults standardUserDefaults] stringForKey:@"username"];
NSString *password = [[NSUserDefaults standardUserDefaults] stringForKey:@"password"];
NSString *restapi = [[NSUserDefaults standardUserDefaults] stringForKey:@"restapi"];

Continue reading

iOS: creating a Login Module and passing data between scenes

Scenario: from a storyboard scene present a login module that consists of two scenes: a login scene and a ‘waiting while logging in’ scene. The presented login module returns a response object to the presenting storyboard scene.

The login module consists of two xib files, each xib file has its own view controller:
1. LoginViewController1, with a login screen and a submit button, is the presenting LoginViewController for the presented LoginViewController2,
2. LoginViewController2, with a waiting gif, is the presented LoginViewController for the presenting LoginViewController.

Both presented LoginViewControllers are passing data back to their presenting viewControllers. For this purpose the presented LoginViewControllers define (1) a delegate protocol, (2) a delegate property and (3) call the presenting LoginViewController’s delegate protocol method implementation, and the presenting LoginViewControllers (1) implement the delegate protocol methods and (2) are set to be the delegate of the presented LoginViewController.


// LoginViewController1.h
#import "LoginViewController2.h"
#import "LoginResponse.h"
@class LoginViewController1;
@protocol LoginViewController1Delegate <NSObject>
/**
* the delegate methods must be implemented by the presenting view controller
*/
- (void)returnFrom:(LoginViewController1 *)controller withResponse:(LoginResponse *)response;
@end
@interface LoginViewController1 : UIViewController <LoginViewController2Delegate>
@property (weak, nonatomic) IBOutlet UITextField *usernameTextfield;
@property (weak, nonatomic) IBOutlet UITextField *passwordTextfield;
- (IBAction)loginButtonPressed:(id)sender;
@end


// LoginViewController1.m
#import "LoginViewController1.h"
#import "LoginViewController2.h"
@interface LoginViewController1 ()
@end
@implementation LoginViewController1
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.passwordTextfield.secureTextEntry = YES;
}
- (IBAction)loginButtonPressed:(id)sender {
NSLog(@"loginButtonPressed-1");
LoginViewController2 *vc2 = [[LoginViewController2 alloc] initWithNibName:@"LoginViewController2" bundle:nil];
vc2.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
vc2.delegate = self;
[self presentViewController:vc2 animated:YES completion:nil];
//[self presentModalViewController: vc2 animated:YES];
}
- (void)returnFrom:(LoginViewController2 *)controller withResponse:(LoginResponse *)response
{
// note that the dismiss is defined within the completion callback to prevent 'dismiss while being presented' error
[controller dismissViewControllerAnimated:YES completion:^{
LoginResponse *response1 = [[LoginResponse alloc] init];
response1 = response;
[self.delegate returnFrom:self withResponse:response1];
}];
}
@end

If you are using a StoryBoard in stead of separate xib files, you replace the loginButtonPressed action with a modal segue, and in the prepareForSegue you set the delegate for the destinationViewController.


- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
LoginViewController2 *vc2 =[segue destinationViewController];
vc2.delegate = self;
}


// LoginViewController2.h
#import "LoginResponse.h"
@class LoginViewController2;
@protocol LoginViewController2Delegate <NSObject>
/**
* the delegate methods must be implemented by the presenting view controller
*/
- (void)returnFrom:(LoginViewController2 *)controller withResponse:(LoginResponse *)response;
@end
@interface LoginViewController2 : UIViewController
/**
* The presenting view controller must be set to be the delegate and extend the presented class
*/
@property (weak, nonatomic) id delegate;
@end


// LoginViewController2.m
#import "LoginViewController2.h"
@interface LoginViewController2 ()
@end
@implementation LoginViewController2
- (void) receivedLoginResponseFromServer
{
LoginResponse *response = [[LoginResponse alloc] init];
response.authorizationToken = @"authToken from vc2";
[self.delegate returnFrom:self withResponse:response];
@end

The view controller in the storyboard that calls the login module must implement the protocol.


// MainViewController.h
#import "LoginViewController1.h"
@interface MainViewController : UIViewController <LoginViewController1Delegate>
@end


// MainViewController.m
#import "MainViewController.h"
#import "LoginViewController1.h"
@interface MainViewController ()
@end
@implementation MainViewController
@synthesize authToken;
- (void) viewDidAppear:(BOOL)animated
{
NSLog(@"mvc viewDidAppear");
if(![self isLoggedIn]){
NSLog(@"not logged in");
[self promptLogin];
}else{
NSLog(@"is logged in");
}
}
-(void) promptLogin
{
LoginViewController1 *vc1 = [[LoginViewController1 alloc] initWithNibName:@"LoginViewController1" bundle:nil];
vc1.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
vc1.delegate = self;
[self presentViewController:vc1 animated:YES completion:nil];
}
- (void)returnFrom:(LoginViewController1 *)controller withResponse:(LoginResponse *)response
{
NSLog(@"mvc response: %@", response.authorizationToken);
self.authToken = response.authorizationToken;
[controller dismissViewControllerAnimated:YES completion:nil];
}

If you are using a LoginStoryBoard instead of separate xib files, the promptLogin method would look as follows.

-(void) promptLogin
{
UIStoryboard *loginStoryBoard = [UIStoryboard storyboardWithName:@"LoginStoryboard" bundle:nil];
LoginViewController1 *vc1 = [loginStoryBoard instantiateInitialViewController];
vc1.delegate = self;
vc1.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
vc1.delegate = self;
[self presentViewController:vc1 animated:YES completion: nil];
}

Download:
LoginModule_ios.zip

iOS: UIViewController lifecycle in a StoryBoard

A storyboard has 3 scenes each with their own UIViewController: vc1, vc2 and vc3. vc1 contains a login button. The login button performs a segue to vc2. vc3 is not connected to vc1 or vc2.

Scenario 1: load the storyboard.

vc1 – loadView
vc1 – viewDidLoad
vc1 – viewWillAppear
vc1 – viewWillLayoutSubviews
vc1 – viewDidLayoutSubviews
vc1 – viewDidAppear

Scenario 2: load the storyboard and press the login button.

vc1 – loadView
vc1 – viewDidLoad
vc1 – viewWillAppear
vc1 – viewWillLayoutSubviews
vc1 – viewDidLayoutSubviews
vc1 – viewDidAppear
vc1 – prepareForSegue
vc2 – loadView
vc2 – viewDidLoad
vc1 – viewWillDisappear
vc2 – viewWillAppear
vc2 – viewWillLayoutSubviews
vc2 – viewDidLayoutSubviews
vc1 – viewWillLayoutSubviews
vc1 – viewDidLayoutSubviews
vc2 – viewDidAppear
vc1 – viewDidDisappear
Continue reading