domingo, 22 de septiembre de 2013

Múltiples idiomas en tiempo de ejecución de la App.

Aunque no es muy común, en alguna ocasión puede que necesitemos que el usuario cambie el idioma de la App sin necesidad de cambiar el idioma del dispositivo. Hay varias soluciones en diferentes foros pero yo voy a explicar la que he utilizado en un proyecto recientemente, el cuento infantil brote brote (cuando esté publicada dejaré el link de la App). Es imprescindible que el proyecto tenga los diferentes Localizable.strings con las traducciones.

1. Creamos un contexto para la App. En esta ocasión, yo lo he incluido en un singleton de contexto ya que me influye en otros factores que no vienen al caso, pero se puede hacer perfectamente en NSUserDefault o en el mismo manager de idoma. Copio el archivo de cabecera seguido del de implementación.


#import <Foundation/Foundation.h>

@interface AppContext : NSObject

@property (nonatomic, strong) NSString *language;

+ (AppContext *)sharedInstance;

@end

A continuación el código de implementación:


#import "AppContext.h"

static AppContext *appContext = nil;

@implementation AppContext

@synthesize language;

+ (void)initialize
{
    NSAssert(self == [AppContext class],@"Esto es un singleton. Se explicará en posteriores artículos");
    appContext = [AppContext new];
}

+ (AppContext *)sharedInstance
{
    return appContext;
}

@end


2. Creamos nuestro manager de idioma. Será el encargado de decirle al path de la App qué carpeta de recurso quiere para el idioma.


#import <Foundation/Foundation.h>

@interface LanguageManager : NSObject

+(NSString*)languageSelectedStringForKey:(NSString*)key;

@end


A continuación el código de implementación:


#import "LanguageManager.h"
#import "AppContext.h"  

@implementation LanguageManager

+(NSString*)languageSelectedStringForKey:(NSString*)key
{
NSString *path;
    path = [[NSBundle mainBundle] pathForResource:[[AppContext sharedInstance] language] ofType:@"lproj"];
NSBundle* languageBundle = [NSBundle bundleWithPath:path];
NSString* string=[languageBundle localizedStringForKey:key value:@"" table:nil];
return string;
}

@end

3. Por último en el controlador encargado de modificar el idioma añadimos el método:

- (void)selectLanguage
{
    if(isLanguageSelected)
    {
        if([[[AppContext sharedInstance] language] isEqualToString:SPANISH])
            [[AppContext sharedInstance] setLanguage:ENGLISH];
        else if([[[AppContext sharedInstance] language] isEqualToString:ENGLISH])
            [[AppContext sharedInstance]setLanguage:DUTCH];
        else if ([[[AppContext sharedInstance] language] isEqualToString:DUTCH])
            [[AppContext sharedInstance] setLanguage:CHINESE];
        else
            [[AppContext sharedInstance] setLanguage:SPANISH];
            
    }
    else
    {
        NSString *language = [[NSLocale preferredLanguages] objectAtIndex:0];
        
        if([language isEqualToString:@"es"])
        {
            [[AppContext sharedInstance] setLanguage:SPANISH];
        }
        else if ([language isEqualToString:@"de"])
        {
            [[AppContext sharedInstance] setLanguage:DUTCH];
        }
        else if([language isEqualToString:@"zh"])
        {
            [[AppContext sharedInstance] setLanguage:CHINESE];
        }
        else
        {
            [[AppContext sharedInstance] setLanguage:ENGLISH];
        }
    }
}

En nuestro caso como en nuestro mismo controlador queremos que se modifiquen algunas etiquetas

- (void)changeLanguage
{
    isLanguageSelected = TRUE;
    [self selectLanguage];
    
    [authorLabel setText:[LanguageManager languageSelectedStringForKey:@"authorLabel"]];
    [auxLabel setText:[LanguageManager languageSelectedStringForKey:@"titleAuxLabel"]];
    [titleLabel setText:[LanguageManager languageSelectedStringForKey:@"titleLabel"]];
    [subTitleLabel setText:[LanguageManager languageSelectedStringForKey:@"subTitleLabel"]];
    [languageButtonLabel setText:[LanguageManager languageSelectedStringForKey:@"languageTitle"]];
    [startButtonLabel setText:[LanguageManager languageSelectedStringForKey:@"startButton"]];
    [playButtonLabel setText:[LanguageManager languageSelectedStringForKey:@"playButton"]];
}





miércoles, 24 de julio de 2013

Descarga asíncrona de imágenes desde una URL

Una de las reglas que debes seguir a rajatabla en la descarga de datos es la siguiente:
"Las descargas de datos siempre serán ASÍNCRONAS"
Si yo fuese responsable de la contratación de algún desarrollador no haría las estúpidas preguntas a las que estamos acostumbrados como ¿ Cuánto te gustaría ganar? o  ¿ Cómo te ves dentro de 5 años? . Sin embargo, cogería un Mac, abriría el XCode y pediría que me hiciese una descarga de datos a través de alguna petición http. Si el candidato escribiese el siguiente código y me dijese que ya está hecho, la entrevista terminaría en ese preciso instante.

NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://www.ohmyapps.es/images/xxxxxx.xxx"]];

Como no quiero escribir artículos largos dejaré para otro día el artículo acerca de NSURLConnection para descargas asíncronas y me centraré en un código que aparece a menudo en diferentes búsquedas por la red y puede resultar algo complicado ya que utiliza la tecnología de GCD (Grand Central Dispatch) para dispositivos multinúcleos y bloques. Este código sólo es válido para aplicaciones superiores a iOS 4.x (aunque ya no creo que nadie desarrolle para esas versiones). Deberemos seguir los siguientes pasos:

1. Copiamos el siguiente código en el archivo de cabecera:


void UIImageFromURL(NSURL * URL, void (^imageBlock)(UIImage * image), void (^errorBlock)(void));

2. Copiamos el siguiente código en el archivo de implementación:


void UIImageFromURL(NSURL * URL, void (^imageBlock)(UIImage * image), void (^errorBlock)(void) )
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^(void)
        {
            NSData * data = [[NSData alloc] initWithContentsOfURL:URL];
            UIImage * image = [[UIImage alloc] initWithData:data];
            dispatch_async( dispatch_get_main_queue(), ^(void){
                if( image != nil )
                {
                    imageBlock( image );
                }
                else
                {
                    errorBlock();
                }
            });
        });
}

Algún observador dirá ,"Pero si utilizas el mismo código que dices que no se debe utilizar nunca", a lo cual yo contestaré, sí pero con la salvedad de el método dispatch_async(dispatch_queue_t queue, ^(void)block);. Y qué hace este método os preguntaréis. Pues simplemente le pasa un bloque de código para su ejecución asíncrona en una cola. 

3. Finalmente utilizamos el método donde lo necesitemos


UIImageFromURL( [NSURL URLWithString:@"http://www.ohmyapps.es/images/xxxxxx.xxx"], ^( UIImage * image )
                   {
                       //Aquí ejecutamos lo que queramos con nuestra imagen
                   }, ^(void){
                       NSLog(@"error!");
                       //Bloque para ejecutar en caso de error
                   });
    }


Para las próximas entradas veremos otra manera de descarga de datos asíncrona y el uso de alguna librería interesante.

miércoles, 3 de julio de 2013

Añadir una fuente externa

Más tarde o más temprano, nos encontraremos con que un diseñador (un cliente no suele entrar a ese nivel detalle) nos pedirá un tipo de fuente poco habitual. Si todavía no somos muy experimentados se nos puede ocurrir la feliz idea de añadir la fuente como otra cualquiera,

UIFont *font = [UIFont fontWithName:@"Fortheloveofhate" size:17.];

Si además, no es una fuente muy extravagante y estamos un poco verdes en todo el tema relacionado con diseño no sabremos si se ha tomado correctamente, ya que iOS nos muestra una fuente por defecto.

Por lo tanto para añadir una fuente externa debemos seguir los siguientes pasos:

1.   Añadimos el ttf a una carpeta de recursos. En el .plist del proyecto añadimos una fila con el key Fonts provided by application de tipo NSArray. A éste, le añadiremos un item de tipo string y value el nombre de la fuente, en este caso Fortheloveofhate.ttf.





2. Ahora simplemente tenemos que crear la fuente y añadirla donde queramos:

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0.,0.,150.,50.)];
    [label setBackgroundColor:[UIColor clearColor]];
    [label setText:@"Oh My Apps!"];
    
    UIFont *font = [UIFont fontWithName:@"Fortheloveofhate" size:17.];
    [label setFont:font];
    [label setCenter:self.view.center];


NOTA: Si queremos conocer todas las fuentes que podemos utilizar en nuestro dispositivo podemos añadir el siguiente código:

    NSArray *fonts = [UIFont familyNames];
    for(UIFont *font in fonts)
    {
        NSLog(@"%@",font);
    }


viernes, 28 de junio de 2013

Clases sin ARC en un proyecto ARC.

No hace mucho tiempo, en el desarrollo para iOS, los desarrolladores debíamos tener el control de la memoria de los objetos creados. Pese al rechazo inicial de algunos desarrolladores (yo me incluía) la experiencia me ha demostrado que pese a conseguir dominar la gestión de la memoria en mis Apps, desde que migré a ARC, mis preocupaciones han disminuido. Aún así, hay una gran cantidad de clases y códigos que ya tenía creados y por cualquier motivo no me interesaba migrar a ARC. Si quisiera incluir esos códigos en un proyecto ARC y los ejecutase me aparecerían los siguientes errores.



Para solucionar este problema, basta con añadir la instrucción -fno-objc-arc, en el archivo de implementación que deseemos que no compile con ARC, como muestro en la siguiente imagen.


Ahora nuestro código se ejecutará sin ningún problema.


jueves, 27 de junio de 2013

Icono sin efecto de brillo.

Sólo hay que echar un vistazo a la pantalla de nuestro dispositivo iOS para ver como algunos iconos de aplicaciones aparecen con un efecto de brillo y otros no. Este efecto lo añade Apple por defecto y para eliminarlo tenemos que seguir los siguientes pasos:

1. Seleccionamos el target de nuestro proyecto, y pulsamos sobre la pestaña de info. Añadimos al plist que nos aparece una fila.


2. Esta fila será de tipo booleano con la key Icon already includes gloss effects y el value YES.



3. Y si volvemos a ejecutar el proyecto podremos ver el icono de nuestra App sin el brillo por defecto.

Icono con brillo
Icono con efecto brillo

Icono sin efecto brillo


NOTA: es recomendable eliminar la App si ya está instalada antes de ejecutar para asegurarnos de que pinta correctamente el icono.

miércoles, 26 de junio de 2013

Controladores nativos de Facebook y Twitter en iOS 6.x. Social framework segunda parte.

La integración de este controlador  ha sido muy sencilla (ver primera parte). Pero tanto facebook como Twitter nos ofrecen otra serie de funciones como seguir, compartir, etc. Para ello necesitaremos acceder a los datos de nuestra cuenta desde nuestras aplicaciones nativas sociales. Seguiremos los siguientes pasos:

1. Adjuntamos el framework Accounts a nuestro proyecto.



2. Importamos las cabeceras a nuestro archivo de cabecera.

#import <Accounts/Accounts.h>

3. Añadimos el siguiente código en el método donde queramos implementar la función (en este caso se toma como ejemplo el "follow" de Twitter).

if([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])
    {
        ACAccountStore *accountStore = [[ACAccountStore alloc] init];
        ACAccountType *twitterAccountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
        [accountStore requestAccessToAccountsWithType:twitterAccountType options:nil completion:^(BOOL granted, NSError *error)
        {
            if(granted)
            {
                NSArray *accountsArray = [accountStore accountsWithAccountType:twitterAccountType];
                
                // En caso de tener más de una cuenta, aquí se debería integrar el código
                if ([accountsArray count] > 0)
                {
                    ACAccount *twitterAccount = [accountsArray objectAtIndex:0];
                    
                    NSMutableDictionary *twitterApiDictionary = [[NSMutableDictionary alloc] init];
                    [twitterApiDictionary setValue:@"OhMyAppss" forKey:@"screen_name"];
                    [twitterApiDictionary setValue:@"true" forKey:@"follow"];
                    
                    SLRequest *twitterRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:[NSURL URLWithString:@"http://api.twitter.com/1/friendships/create.json"] parameters:twitterApiDictionary];
                         
                    [twitterRequest setAccount:twitterAccount];
                         
                    [twitterRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
                    {
                        NSLog(@"%@",[NSString stringWithFormat:@"HTTP response status: %i", [urlResponse statusCode]]);

                 //Aquí se deben estudiar los posibles casos de error

                    }];
                }
            }
        }];
    }

Para conocer todas las funciones que nos ofrecen Twitter y Facebook deberemos consultar su documentación: 



martes, 25 de junio de 2013

Controladores nativos de Facebook y Twitter en iOS 6.x. Social framework primera parte.

  Una de las novedades que trajo consigo iOS 6.0 fue el Social framework. Con él, podemos incluir el controlador nativo de redes sociales, de una manera muy sencilla. Aquellos que nos tuvimos que pelear con los SDKs de Facebook y Twitter, estaremos eternamente agradecidos. Los pasos para dicha integración son los siguientes:

1.  Añadir la librería Social.framework a nuestro proyecto. No olvidéis marcarlo como "optional" si la aplicación está habilitada para dispositivos anteriores a iOS 6.0.



2. Importamos las cabeceras del framework a nuestro archivo de cabecera.

#import <Social/Social.h>
3. Añadimos el siguiente código al método que ejecute la acción de social media deseada:


if([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])
    {
        SLComposeViewController *twitterVC = [SLComposeViewController  composeViewControllerForServiceType:SLServiceTypeTwitter];
    
        [twitterVC setInitialText:NSLocalizedString(@"App creada por Oh My Apps!", nil)];
        [twitterVC setTitle:@"Oh My Apps!"];
        [twitterVC addImage:[UIImage imageNamed:@"oma_icon"]];
        [twitterVC addURL:[NSURL URLWithString:@"http://www.ohmyapps.es"]];
    
        [self presentViewController:twitterVC animated:YES completion:nil];
    }


Podremos utilizar el controlador para Twitter con el tipo SLServiceTypeTwitter, para facebook con el tipo SLServiceTypeFacebook y para la red social china Seibo con el tipo SLServiceTypeSinaWeibo.

Al ejecutar nos deberá aparecer el controlador nativo.