iOS smooth signature capture example using quadratic bezier curve

Signatures are stylized scripts associating them to a given individual and primarily used for the purpose of displaying intent of that individual in regards to a given document by having his or her signature. Now with the advent of the mobile devices such as iPhone and iPad there is an everyday demand to capture individual's signature on electronic documents such as PDF and mobile applications. In this example we take advantage of the UITouch and UIGraphics class to capture the signature in an PNG image. To make our signature smooth we use the core graphics quadratic Bézier curve method instead of drawing straight lines between our touch points.

iOS smooth signature capture example iOS signature capture with name iOS signature using quadratic bezier curve

Interface file for the App Delegate - SignatureCaptureAppDelegate.h

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

@interface SignatureCaptureAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UINavigationController *navigationController;
@property (strong, nonatomic) MyViewController *myViewController;

@end

Implementation file for the App Delegate - SignatureCaptureAppDelegate.m

#import "SignatureCaptureAppDelegate.h"

@implementation SignatureCaptureAppDelegate

@synthesize navigationController;
@synthesize myViewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    
    //create the navigation controller and add the controllers view to the window
    navigationController = [[UINavigationController alloc] init];
    [self.window addSubview:[self.navigationController view]];
    
    //check if the viewcontroller exists, otherwise create it
    if(self.myViewController == nil)
    {
        MyViewController *inputView = [[MyViewController alloc] init];
        self.myViewController = inputView;
    }
    
    //push the viewcontroller into the navigation view controller stack
    [self.navigationController pushViewController:self.myViewController animated:YES];
    
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;

}


@end

Interface file for the Signature capture View Controller - MyViewController.h

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

@interface MyViewController : UIViewController <UIAlertViewDelegate>

@property (nonatomic, strong) UIImageView *mySignatureImage;
@property (nonatomic, assign) CGPoint lastContactPoint1, lastContactPoint2, currentPoint;
@property (nonatomic, assign) CGRect imageFrame;
@property (nonatomic, assign) BOOL fingerMoved;
@property (nonatomic, assign) float navbarHeight;

@property (strong, nonatomic) DisplaySignatureViewController *displaySignatureViewController;

@end

Implementation file for the Signature capture View Controller - MyViewController.m

#import "MyViewController.h"

@interface MyViewController ()

@end

@implementation MyViewController

@synthesize mySignatureImage;
@synthesize lastContactPoint1, lastContactPoint2, currentPoint;
@synthesize imageFrame;
@synthesize fingerMoved;
@synthesize navbarHeight;

@synthesize displaySignatureViewController;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
 //set the title of the navigation view
    [self.navigationItem setTitle:@"Sign here"];
    
    //create a save button in the navigation bar
    UIBarButtonItem *myButton = [[UIBarButtonItem alloc]
                                 initWithTitle:@"Save"
                                 style:UIBarButtonItemStylePlain
                                 target:self
                                 action:@selector(saveSignature:)];
    [self.navigationItem setRightBarButtonItem:myButton];
    //set the view background to light gray
    self.view.backgroundColor = [UIColor lightGrayColor];

    //get reference to the navigation frame to calculate navigation bar height
    CGRect navigationframe = [[self.navigationController navigationBar] frame];
    navbarHeight = navigationframe.size.height;
    
    //create a frame for our signature capture based on whats remaining
    imageFrame = CGRectMake(self.view.frame.origin.x+10,
                                self.view.frame.origin.y-5,
                                self.view.frame.size.width-20,
                                self.view.frame.size.height-navbarHeight-30);
    
    //allocate an image view and add to the main view
    mySignatureImage = [[UIImageView alloc] initWithImage:nil];
 mySignatureImage.frame = imageFrame;
    mySignatureImage.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:mySignatureImage];
    
    
}

//when one or more fingers touch down in a view or window
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 
    //did our finger moved yet?
 fingerMoved = NO;
    UITouch *touch = [touches anyObject];
 
    //just clear the image if the user tapped twice on the screen
 if ([touch tapCount] == 2) {
  mySignatureImage.image = nil;
  return;
 }
    
    //we need 3 points of contact to make our signature smooth using quadratic bezier curve
 currentPoint = [touch locationInView:mySignatureImage];
    lastContactPoint1 = [touch previousLocationInView:mySignatureImage];
    lastContactPoint2 = [touch previousLocationInView:mySignatureImage];
    
}


//when one or more fingers associated with an event move within a view or window
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    
 //well its obvious that our finger moved on the screen
    fingerMoved = YES;
 UITouch *touch = [touches anyObject];
    
    //save previous contact locations
    lastContactPoint2 = lastContactPoint1;
    lastContactPoint1 = [touch previousLocationInView:mySignatureImage];
    //save current location
 currentPoint = [touch locationInView:mySignatureImage];
   
    //find mid points to be used for quadratic bezier curve
    CGPoint midPoint1 = [self midPoint:lastContactPoint1 withPoint:lastContactPoint2];
    CGPoint midPoint2 = [self midPoint:currentPoint withPoint:lastContactPoint1];
 
    //create a bitmap-based graphics context and makes it the current context
 UIGraphicsBeginImageContext(imageFrame.size);
    
    //draw the entire image in the specified rectangle frame
 [mySignatureImage.image drawInRect:CGRectMake(0, 0, imageFrame.size.width, imageFrame.size.height)];
 
    //set line cap, width, stroke color and begin path
    CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
 CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 3.0f);
 CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.0, 0.0, 0.0, 1.0);
 CGContextBeginPath(UIGraphicsGetCurrentContext());
    
    //begin a new new subpath at this point
    CGContextMoveToPoint(UIGraphicsGetCurrentContext(), midPoint1.x, midPoint1.y);
    //create quadratic Bézier curve from the current point using a control point and an end point 
    CGContextAddQuadCurveToPoint(UIGraphicsGetCurrentContext(),
                                 lastContactPoint1.x, lastContactPoint1.y, midPoint2.x, midPoint2.y);

    //set the miter limit for the joins of connected lines in a graphics context
    CGContextSetMiterLimit(UIGraphicsGetCurrentContext(), 2.0);
    
    //paint a line along the current path
 CGContextStrokePath(UIGraphicsGetCurrentContext());
    
    //set the image based on the contents of the current bitmap-based graphics context
 mySignatureImage.image = UIGraphicsGetImageFromCurrentImageContext();
    
    //remove the current bitmap-based graphics context from the top of the stack
 UIGraphicsEndImageContext();
 
 //lastContactPoint = currentPoint;
    
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
 
 UITouch *touch = [touches anyObject];
 
    //just clear the image if the user tapped twice on the screen
 if ([touch tapCount] == 2) {
  mySignatureImage.image = nil;
  return;
 }
 
 
    //if the finger never moved draw a point 
 if(!fingerMoved) {
  UIGraphicsBeginImageContext(imageFrame.size);
        [mySignatureImage.image drawInRect:CGRectMake(0, 0, imageFrame.size.width, imageFrame.size.height)];
  
        CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
  CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 3.0f);
  CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.0, 0.0, 0.0, 1.0);
  CGContextMoveToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
        CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
  CGContextStrokePath(UIGraphicsGetCurrentContext());
  CGContextFlush(UIGraphicsGetCurrentContext());
  
        mySignatureImage.image = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();
 }
}

//calculate midpoint between two points
- (CGPoint) midPoint:(CGPoint )p0 withPoint: (CGPoint) p1 {
    return (CGPoint) {
        (p0.x + p1.x) / 2.0,
        (p0.y + p1.y) / 2.0
    };
}


//save button was clicked, its time to save the signature
- (void) saveSignature:(id)sender {
    
    //get reference to the button that requested the action
    UIBarButtonItem *myButton = (UIBarButtonItem *)sender;
    
    //check which button it is, if you have more than one button on the screen
    //you must check before taking necessary action
    if([myButton.title isEqualToString:@"Save"]){
        NSLog(@"Clicked on the bar button");
        
        //display an alert to capture the person's name
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Saving signature with name"
                                    message:@"Please enter your name"
                                    delegate:self
                                    cancelButtonTitle:@"Cancel"
                                    otherButtonTitles:@"Ok", nil];
        [alertView setAlertViewStyle:UIAlertViewStylePlainTextInput];
        [alertView show];
    }
    
}

//some action was taken on the alert view
- (void) alertView:(UIAlertView *)alertView
        clickedButtonAtIndex:(NSInteger)buttonIndex{
    
    //which button was pressed in the alert view
    NSString *buttonTitle = [alertView buttonTitleAtIndex:buttonIndex];
    
    //user wants to save the signature now
    if ([buttonTitle isEqualToString:@"Ok"]){
        NSLog(@"Ok button was pressed.");
        NSLog(@"Name of the person is: %@", [[alertView textFieldAtIndex:0] text]);
        NSString * personName = [[alertView textFieldAtIndex:0] text];
        
        //create path to where we want the image to be saved
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0];
        NSString *filePath = [documentsDirectory stringByAppendingPathComponent:@"MyFolder"];
        
        //if the folder doesn't exists then just create one
        NSError *error = nil;
        if (![[NSFileManager defaultManager] fileExistsAtPath:filePath])
            [[NSFileManager defaultManager] createDirectoryAtPath:filePath
                                    withIntermediateDirectories:NO
                                    attributes:nil
                                    error:&error];
            
        //convert image into .png format.
        NSData *imageData = UIImagePNGRepresentation(mySignatureImage.image);
        NSString *fileName = [filePath stringByAppendingPathComponent:
                              [NSString stringWithFormat:@"%@.png", personName]];
        
        //creates an image file with the specified content and attributes at the given location
        [fileManager createFileAtPath:fileName contents:imageData attributes:nil];
        NSLog(@"image saved");
        
        //check if the display signature view controller doesn't exists then create it
        if(self.displaySignatureViewController == nil){
            DisplaySignatureViewController *displayView = [[DisplaySignatureViewController alloc] init];
            self.displaySignatureViewController = displayView;
        }
        
        //pass the person's name to the next view controller
        self.displaySignatureViewController.personName = personName;
        
        //tell the navigation controller to push a new view into the stack
        [self.navigationController pushViewController:self.displaySignatureViewController animated:YES];


    }
    
    //just forget it
    else if ([buttonTitle isEqualToString:@"Cancel"]){
        NSLog(@"Cancel button was pressed.");
    }
    
}



- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

Interface file for the Signature display View Controller - DisplaySignatureViewController.h

#import <UIKit/UIKit.h>

@interface DisplaySignatureViewController : UIViewController

@property (nonatomic, strong) NSString *personName;
@property (nonatomic, strong) UILabel *signedBy;
@property (nonatomic, strong) UIImageView *mySignatureView;

@end

Implementation file for the Signature display View Controller - DisplaySignatureViewController.m

#import "DisplaySignatureViewController.h"

@interface DisplaySignatureViewController ()

@end

@implementation DisplaySignatureViewController

@synthesize personName;
@synthesize signedBy;
@synthesize mySignatureView;


- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
 //set the title of the navigation view
    [self.navigationItem setTitle:@"Display Signature"];
   
    //create a label to display the name of the person who signed the document
    CGRect myFrame = CGRectMake(10.0f, 0.0f, 300.0f, 30.0f);
    signedBy = [[UILabel alloc] initWithFrame:myFrame];
    signedBy.font = [UIFont boldSystemFontOfSize:16.0f];
    signedBy.textAlignment =  NSTextAlignmentLeft;
    [self.view addSubview:signedBy];
    
    //get reference to the navigation frame to calculate bar height
    CGRect navigationframe = [[self.navigationController navigationBar] frame];
    int navbarHeight = navigationframe.size.height;
    
    //frame for our signature image
    CGRect imageFrame = CGRectMake(self.view.frame.origin.x+10, 30,
                            self.view.frame.size.width-20,
                            self.view.frame.size.height-navbarHeight-30);
    
    //create an image view to display our signature image
    self.mySignatureView = [[UIImageView alloc] init];
    [self.mySignatureView setFrame:imageFrame];
    [self.mySignatureView setContentMode:UIViewContentModeScaleAspectFit];
    [self.view addSubview:self.mySignatureView];
    
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:YES];
    
    //who signed the document
    signedBy.text = [NSString stringWithFormat:@"Signed by: %@", personName];
    
    //create the path to our image file
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:@"MyFolder"];
    NSString *fileName = [filePath stringByAppendingPathComponent:
                          [NSString stringWithFormat:@"%@.png", personName]];
    
    //get the contents of the image file into the image
    UIImage *signature = [UIImage imageWithContentsOfFile:fileName];
    //display our signature image
    mySignatureView.image = signature;
    
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

Related Posts