[iOs] Upload di immagini da app su server

Come primo post dell’anno, vi scrivo una soluzione per l’upload di foto su un vostro server (valida anche per qualsiasi altro tipo di file). Fino a poco tempo fa era possibile utilizzare il framework ASIHTTPRequest, che permetteva in modo agevole di eseguire questa operazione. Tuttavia, essendo tale framework “no-ARC compatibile”, vi consiglio un metodo nuovo che sfrutta la classe “nativa” NSURLConnection, oppure la libreria di terza parte URLConnection (per inserire una comoda progress bar).

Vi scrivo anche lo script PHP da richiamare lato app per l’upload della foto e che ho utilizzato come test (file image_upload.php):

/* image_upload.php */

<?php

//elenco delle estensioni permesse
$extensions_permitted = array("jpg", "png", "JPG", "PNG");

//path assoluto in cui si trova il file di script upload_image.php
$absolute_current_path = getcwd();

//recupero il nome del file dal parametro di input "filename"
$filename = $_POST['filename'];
$response = array();
$error = false;

//recupero l'estensione del file
$ext = pathinfo($_FILES['userfile']['name'], PATHINFO_EXTENSION);

//controllo il nome file
if(empty($filename)){
  $error = true;
  $response['code'] = '500';	
  $response['message'] = ('Nome file non valido!');	
  $response['newfile'] = $filename.'.'.$ext;
}

//controllo l'estensione
if (!in_array($ext, $extensions_permitted)) {
  $error = true;
  $response['code'] = '500';	
  $response['message'] = ('Estensione del file non permessa!');	
  $response['newfile'] = null;
}

if(!$error){
	//NOTA: la seguente directory deve esistere sul proprio server.
	//Assicurarsi di avere i chmod di scrittura
	$uploaddir = $absolute_current_path.'/img/';

	//percorso assoluto del nuovo file
	$uploadfile = $uploaddir . $filename.'.'.$ext;

	if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
    	$response['code'] = '200';	
  		$response['message'] = ('File valido e correttamente salvato!');	
        $response['newfile'] = $filename.'.'.$ext;
	} else {
    	echo "Errore nell'upload del file!\n";
        $response['code'] = '500';	
  		$response['message'] = ('Errore nell\'upload del file!');	
        $response['newfile'] = null;
	}

//echo 'Alcune informazioni di debug:';
//print_r($_FILES);

}

echo json_encode($response);

?>

Di seguito, una pagina di test PHP per controllare che il vostro script di upload sia stato configurato correttamente sul vostro server. NOTA. Vi troverete le immagini nella directory IMG relativa al file image_upload.php.

<html>
<head>
	<title>Upload a file</title>
</head>

<body>
<!-- Tipo di codifica dei dati, DEVE essere specificato come segue -->
<form enctype="multipart/form-data" action="upload_image.php" method="POST">

    <!-- MAX_FILE_SIZE deve precedere campo di input del nome file -->
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />

    <b>Nome file:</b>
    <input type="text" name="filename" value="" />

    <br/>

    <!-- Il nome dell'elemento di input determina il nome nell'array $_FILES -->
    <b>File:</b> <input name="userfile" type="file" />
    <br/>
    <input type="submit" value="Invia File" />
</form>

</body>

</html>

Ecco i codici sorgenti degli script PHP su riportati:

PHP upload script

upload
upload.rar (2 kB)

Template doesn't exists. Use default

Passiamo ora al codice Objective-C per la vostra app client. Ho creato la classe UploaderDelegate, che potete comodamente importare nei vostri progetti e che gestirà l’upload della foto. L’interfaccia è quella che segue:

//
//  UploaderDelegate.h
//
//  Created by Fr@nk on 07/01/14.
//  Copyright (c) 2014. All rights reserved.
//

#import <Foundation/Foundation.h>

@protocol UploaderProtocol <NSObject>

@required
-(void)successUpload;

@optional
-(void)unsuccessUpload;

@optional
-(void)updateProgressBar:(float)progress;

@end

@interface UploaderDelegate : NSObject<NSURLConnectionDataDelegate>{

    NSMutableData *receivedData;

}

//singleton
+ (id)sharedInstance;
-(void)sendImageToServerWithURLPath:(NSString*)pathImage withFilename:(NSString*)filename toServiceURL:(NSString*)serviceURL;

@property (nonatomic, weak) id<UploaderProtocol> delegate;

@end

L’implementazione dell’UploadDelegate è questa:

//
//  UploaderDelegate.m
//
//  Created by Fr@nk on 07/01/14.
//  Copyright (c) 2013. All rights reserved.
//

#import "UploaderDelegate.h"
#import "URLConnection.h"
#import "Configuration.h"

@implementation UploaderDelegate

@synthesize delegate;

static UploaderDelegate *sharedInstance = nil;

// Get the shared instance and create it if necessary.
+ (UploaderDelegate *)sharedInstance {
    if (sharedInstance == nil) {
        sharedInstance = [[super allocWithZone:NULL] init];
    }

    return sharedInstance;
}

- (id)init
{
    self = [super init];

    if (self) {

    }

    return self;
}

-(void)sendImageToServerWithURLPath:(NSString*)pathImage withFilename:(NSString*)filename toServiceURL:(NSString*)serviceURL{

    //controllo se il file esiste
    BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:pathImage];

    if(!fileExists){
        NSLog(@"Error: file not exists!");
        //richiamo l'errorUpload sul Delegate in caso di success
        if (self.delegate && [self.delegate respondsToSelector:@selector(unsuccessUpload)]) {
            [self.delegate unsuccessUpload];
        }
        return;
    }

    //controllo la grandezza del file
    NSError *AttributesError = nil;
	NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:pathImage error:&AttributesError];
	NSNumber *fileSizeNumber = [fileAttributes objectForKey:NSFileSize];
	long fileSize = [fileSizeNumber longValue];
	NSLog(@"File: %@, Size: %ld", pathImage, fileSize);
    if(fileSize<=0){
        NSLog(@"Error: file size incorrect!");
        //richiamo l'errorUpload sul Delegate in caso di success
        if (self.delegate && [self.delegate respondsToSelector:@selector(unsuccessUpload)]) {
            [self.delegate unsuccessUpload];
        }
        return;
    }

    //se la foto è troppo grande, la comprimo
    UIImage *originalImage = [[UIImage alloc]initWithContentsOfFile:pathImage];

    //NSData *dataImage = [NSData dataWithContentsOfURL:[NSURL URLWithString:pathImage]];
    //NSData *dataImage = [[NSFileManager defaultManager] contentsAtPath:pathImage];

    CGFloat compression = 0.9f;
    CGFloat maxCompression = 0.1f;
    int maxFileSize = MAXFILESIZE_IMAGE_UPLOAD;

    //comprimo l'immagine e la salvo in JPEG, se è troppo grande
    NSData *dataImage = UIImageJPEGRepresentation(originalImage, compression);

    while ([dataImage length] > maxFileSize && compression > maxCompression)
    {
        compression -= 0.1;
        dataImage = UIImageJPEGRepresentation(originalImage, compression);
    }

    NSMutableURLRequest *postRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serviceURL]];

    [postRequest setHTTPMethod:@"POST"];

    NSString *boundary = @"BVillage";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
    [postRequest addValue:contentType forHTTPHeaderField: @"Content-Type"];

    NSMutableData *body = [NSMutableData data];

    [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"filename\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[filename dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Disposition: form-data; name=\"userfile\"; filename=\"tmpFile.jpg\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[NSData dataWithData:dataImage]];
    [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    [postRequest setHTTPMethod:@"POST"];
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    // Setting a timeout
    postRequest.timeoutInterval = 60.0;
    [postRequest setHTTPBody:body];

    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    /*[NSURLConnection sendAsynchronousRequest:postRequest
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
     */
    [URLConnection asyncConnectionWithRequest:postRequest completionBlock:^(NSData *data, NSURLResponse *response) {

        NSLog(@"Succeeded! Received %d bytes of data",[receivedData length]);
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

        NSDictionary *responseJSON = [NSJSONSerialization
                                      JSONObjectWithData:data
                                      options:kNilOptions error:nil];

        NSLog(@"Response JSON: %@",responseJSON);

        //if(!error){
            //richiamo il successUpload sul Delegate in caso di success
            if (self.delegate && [self.delegate respondsToSelector:@selector(successUpload)]) {
                [self.delegate successUpload];
            }
            return;
        /*}
        else{
            //richiamo l'errorUpload sul Delegate in caso di success
            if (self.delegate && [self.delegate respondsToSelector:@selector(unsuccessUpload)]) {
                [self.delegate unsuccessUpload];
            }
            return;
        }*/

    } errorBlock:^(NSError *error) {
        ////richiamo l'errorUpload sul Delegate in caso di success
        if (self.delegate && [self.delegate respondsToSelector:@selector(unsuccessUpload)]) {
            [self.delegate unsuccessUpload];
        }
        return;
    } uploadProgressBlock:^(float progress) {
        if (self.delegate && [self.delegate respondsToSelector:@selector(updateProgressBar:)]) {
            [self.delegate updateProgressBar:progress];
        }

    } downloadProgressBlock:^(float progress) {
        //
    }

    ];

}

@end

Come potete vedere, ho utilizzato la libreria URLConnection, per poter mostrare durante il caricamento del file una “progress bar” (come spiegato in questo post: http://messagesenttodeallocatedinstance.wordpress.com/2012/04/10/nsurlconnection-with-blocks/).

Per poter usare la classe UploaderDelegate e procedere all’upload della foto, basta inserire nel vostro codice la seguente chiamata:

//chiamata al wrapper per l'upload dei file sul server
UploaderDelegate *uploaderDelegate = [UploaderDelegate sharedInstance];
uploaderDelegate.delegate = self;

NSString *pathFile = ABSOLUTE_PATH_OF_YOUR_PHOTO;
NSString *photoName = FILENAME_OF_YOUR_PHOTO;

    [uploaderDelegate sendImageToServerWithURLPath:pathFile withFilename:photoName toServiceURL:UPLOAD_IMAGE_URL];

NOTA. E’ importante settare che il pathFile sia il percorso assoluto alla vostra foto (comprensivo di nome del file e di estensione). La variabile photoName è il nome della foto (senza estensione) così come verrà salvata sul vostro server. Al posto dell’etichetta UPLOAD_IMAGE_URL ricordatevi di sostituire la URL del vostro servizio PHP di upload (per esempio, http://www.francescoficetola/upload/image_upload.php)

Ricordatevi di importare la classe suddetta e di settare la “delega” nel vostro ViewController, inserendo l’UploaderProtocol. Come ad esempio:

//
//  MyViewController
//
//  Created by Fr@nk on 07/01/14.
//  Copyright (c) 2014. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "UploaderDelegate.h"
#import "KKProgressToolbar.h"

@interface MyViewController : UIViewController<UploaderProtocol, KKProgressToolbarDelegate>{

    IBOutlet UIImageView *imageView;
    IBOutlet UIButton *imageUploadButton;
    KKProgressToolbar *progressBar;
}

/* metodi di delegate */
-(void)successUpload;
-(void)unsuccessUpload;
-(void)updateProgressBar:(float)progress;
-(IBAction)startProgressBarLoading;
-(IBAction)stopProgressBarLoading;

-(IBAction)sendPhotoToServer:(id)sender;

@property (nonatomic, strong) IBOutlet UIImageView *imageView;
@property (nonatomic, strong) KKProgressToolbar *progressBar;
@property (nonatomic, strong) IBOutlet UIButton *imageUploadButton;

@end

Per poter gestire gli eventi relativi all’upload, occorre definire nel vostro ViewController i seguenti metodi di delegate:

  • successUpload: richiamato dall’UploaderDelegate in caso di upload con successo
  • unsuccessUpload: richiamato dall’UploadDelegate in caso di errore nell’upload
  • updateProgressBar: richiamato dall’UploadDelegate durante l’upload per aggiornare la percentuale di caricamento sulla progress bar
  • startProgressBarLoading: da richiamare, lato app, per inizializzare la progress bar
  • stopProgressBarLoading: da richiamare, lato app, per stoppare la progress bar

NOTA. Per gestire la progress bar ho utilizzato la seguente libreria: KKProgressToolbar

#pragma mark Uploader methods

-(void)updateProgressBar:(float)progress{
    self.progressBar.progressBar.progress = progress;
}

-(void)successUpload{
    NSLog(@"successUpload");
    //in caso di successo aggiorno rimuovo la progress bar
    [self stopProgressBarLoading];

}

-(void)unsuccessUpload{

    NSLog(@"unsuccessUpload");
}

#pragma StatusBarLoading

-(void)addProgressBar{

    //aggiungo la progress view:
    CGRect progressBarFrame = CGRectMake(0, self.view.frame.size.height-100, self.view.frame.size.width, 44);
	self.progressBar = [[KKProgressToolbar alloc] initWithFrame:progressBarFrame];
	self.progressBar.actionDelegate = self;
    self.progressBar.progressBar.progress = 0;
    self.progressBar.statusLabel.text = @"Loading from server...";
	[self.view addSubview:self.progressBar];

    [self startProgressBarLoading];

}

- (void)didCancelProgressBarButtonPressed:(KKProgressToolbar *)toolbar {
    [self stopProgressBarLoading];
    //TODO: eliminare la richiesta di upload...
}

- (IBAction)startProgressBarLoading  {

    [self.progressBar show:YES completion:^(BOOL finished) {
        //Code
    }];

}

- (IBAction)stopProgressBarLoading {
    [self.progressBar hide:YES completion:^(BOOL finished) {
        //code
    }];

}

Creative Commons License
This work by Francesco Ficetola is licensed under a Creative Commons Attribution 4.0 International License.
Based on a work at www.francescoficetola.it.
Permissions beyond the scope of this license may be available at http://www.francescoficetola.it/2014/01/06/ios-upload-di-immagini-da-app-su-server/.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *


− 7 = due