tag:blogger.com,1999:blog-48610313970203855912024-10-24T22:18:40.636+02:00iPhone, iPad y AndroidBlog especializado en plataformas móvilesAngel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]Blogger11125tag:blogger.com,1999:blog-4861031397020385591.post-39839329199161313312012-05-19T22:12:00.003+02:002012-05-19T22:12:54.275+02:00Last post in this URL<div dir="ltr" style="text-align: left;" trbidi="on">
This is the last post in this blog. You can continue reading my new posts at <a href="http://angelolloqui.com/blog">http://angelolloqui.com/blog</a> where I have already published some interesting articles.<br />
<br />
Este es mi último post en este blog. Puedes seguir leyendo mis nuevos posts en <a href="http://angelolloqui.com/blog">http://angelolloqui.com/blog</a>, donde ya he publicado algunos artículos interesantes.</div>Angel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]0tag:blogger.com,1999:blog-4861031397020385591.post-4627911409221031342011-03-08T10:51:00.005+01:002012-05-19T22:05:51.808+02:00Manteniendo compatibilidad con versiones anteriores de iOS<div dir="ltr" style="text-align: left;" trbidi="on">
<br />
<span style="font-size: 21px;"><b>Este post se ha movido de forma permanente a <a href="http://angelolloqui.com/blog/12-Manteniendo-compatibilidad-con-versiones-anteriores-de-iOS">http://angelolloqui.com/blog/12-Manteniendo-compatibilidad-con-versiones-anteriores-de-iOS</a><a href="http://www.blogger.com/"></a></b></span><br />
<div>
<span style="font-size: 21px;"><br /></span></div>
<br />
<br />
Es muy habitual encontrarnos con la necesidad de dar soporte a usuarios que no estén actualizados con el último iOS en nuestras aplicaciones iPhone o iPad, pero los SDKs nuevos dan soporte únicamente para un conjunto muy reducido de versiones anteriores, lo que normalmente no es suficiente.<br />
<a name='more'></a><br />
Ante esto, muchos developers no tienen muy claro como conseguirlo, y recurren a soluciones muy poco prácticas (y con varios problemas). La más habitual es tener diferentes SDKs instalados en su máquina, para compilar usando uno u otro dependiendo del iOS al que quieran dar compatibilidad. Esta solución, además de ser poco elegante, ocupar mucho espacio en disco y dar problemas a la hora de subir la app al AppStore, tiene otros dos problemas muy grandes:<br />
<ol>
<li>La arquitectura de los simuladores cambia cada cierto tiempo. Esto hace que una librería compilada para un simulador en la 4.3 por ejemplo, no pueda ejecutarse en un simulador en la 3.0. Si usamos librerías de terceros (como Flurry por ejemplo) podemos vernos en un problema para conseguir ejecutar nuestra aplicación en un simulador. Seguimos pudiendo ejecutar en dispositivo, pero la falta de simulador puede hacer el desarrollo mucho más lento.</li>
<li>No podemos utilizar funcionalidad agregada en SDKs posteriores. Esto parece de perogrullo, pero si compilamos con un SDK3.0 no podremos hacer uso de cosas como FastApp Switching y similares.</li>
</ol>
<br />
Todo esto tiene una solución mucho más sencilla y elegante, que es <span style="font-weight: bold;">utilizando el llamado "Deployment Target".</span><br />
<br />
Pero antes de nada, voy a explicar la diferencia entre algunos conceptos que no siempre están claros y pueden mezclarse:<br />
<ul>
<li><span style="font-weight: bold;">Versión de iOS</span>: iOS es el sistema operativo que corre en el dispositivo del usuario. Aquí caben muchísimas opciones, y nuestros usuarios estarán en una u otra. Así pues, si nuestra aplicación da soporte para versiones iOS3.0 o posterior, cualquier usuario con una versión iOS3.0 o posterior podrá ejecutar nuestra app. Según la versión de su iOS, el usuario podrá tener unas funcionalidades u otras (por ejemplo, los mapas se introdujeron en la 3.0 o el FastAppSwitch en la 4.0).</li>
<li><span style="font-weight: bold;">Versión de SDK instalada</span>: Este concepto se refiere a la versión del SDK de desarrollo que te descargaste e instalaste en tu máquina. Si te lo has bajado de la página de Apple, será la última versión disponible. (Por ejemplo, ahora mismo estamos en la 4.3)</li>
<li><span style="font-weight: bold;">Base SDK</span>: Establece con que versión de SDK se va a compilar un proyecto. El BaseSDK siempre tiene que ser igual o inferior a tu versión de SDK instalada. Normalmente, un SDK incluye varios Base SDKs anteriores, por si el usuario desea compilar con alguno previo.</li>
<li><span style="font-weight: bold;">Deployment Target</span>: Establece a partir de qué versión de iOS podrá ejecutarse tu aplicación. El deployment target por defecto se establece igual al BaseSDK utilizado, pero puede cambiarse a uno anterior.</li>
</ul>
<br />
Una vez explicados estos conceptos, que en muchos casos parecen iguales o pueden resultar confusos, voy a tratar de explicar como solucionar el problema de dar soporte a usuarios con versiones previas de forma elegante , sin perder la compatibilidad con librerías compiladas para simulador y pudiendo dar a usuarios con versiones avanzadas de iOS una funcionalidad extra de los nuevos SDKs.<br />
El truco está en configurar correctamente el DeploymentTarget. Para ello, basta con ir a las propiedades del proyecto (abre XCode, pincha en la raiz del proyecto con el botón derecho, "Get Info", Pestaña "Build") y busca el campo "iOS Deployment Target". Verás que te da un listado de versiones de iOS mucho más extenso del que tienes en las opciones del BaseSDK. Seleccionas tu deployment target deseado y listo.<br />
<br />
La dificultad de esta solución viene cuando tu app usa alguna funcionalidad que se introdujo en alguna versión de SDK posterior a la que has seleccionado en Deployment Target.<br />
Digamos que pretendes usar Notificaciones locales (introducidas en el 4.2), pero quieres dar soporte a usuarios que sigan en la 3.0. En este caso, puedes compilar con un BaseSDK 4.2 y configurar el Deployment Target a la 3.0. Hasta aquí todo bien (el compilador se lo traga y todo parece funcionar bien en simulador y en un dispositivo con iOS 4.2 o posterior). El problema está en que en el momento en que tu app trate de ejecutar el código asociado a las notificaciones locales, la app se cerrará si el iOS del usuario no es el 4.2 o posterior, ya que no será capaz de ejecutar ese código.<br />
<span style="font-weight: bold;">¿Cómo nos protegemos?</span> la solución pasa por estar muy atento a qué métodos utilizas que no estuviesen presentes en el SDK 3.0, para comprobar si existen antes de usarlos en tiempo de ejecución. Para ello, en caso de ser una clase, debes comprobar si la clase existe antes de usarla mediante la ejecución de "NSClassFromString", mientras que deberás usar el "respondsToSelector" en caso de tratarse de un método.<br />
<br />
Ej. 1 - Comprobación de clases - ¿Dispositivo soporta Local Notifications?<br />
<div class="wp_syntax">
<div class="code">
<pre class="objc" style="font-family: monospace;"><span style="color: #a61390;">if</span> <span style="color: #002200;">(</span><span style="color: #a61390;">NSClassFromString</span><span style="color: #002200;"></span><span style="color: #002200;">(<span style="color: #cc0000;">@"UILocalNotification"</span>)!=</span><span style="color: #a61390;">nil</span><span style="color: #002200;"></span><span style="color: #002200;"></span><span style="color: #002200;">)</span>
<span style="color: #002200;">{</span>
<span style="color: #11740a; font-style: italic;">// Soporta LocalNotifications</span>
<span style="color: #002200;">}</span>
<span style="color: #a61390;">else</span>
<span style="color: #002200;">{</span>
<span style="color: #11740a; font-style: italic;">// No soporta Local Notifications</span>
<span style="color: #002200;">}</span></pre>
</div>
</div>
<br />
Ej. 2 - Comprobación de métodos - ¿Dispositivo soporta multitasking:?<br />
<div class="wp_syntax">
<div class="code">
<pre class="objc" style="font-family: monospace;"><span style="color: #a61390;">if</span> <span style="color: #002200;">(</span><span style="color: #002200;">[</span><span style="color: #002200;">[</span>UIDevice currentDevice<span style="color: #002200;">]</span> respondsToSelector<span style="color: #002200;">:</span><span style="color: #a61390;">@selector</span><span style="color: #002200;">(</span>isMultitaskingSupported<span style="color: #002200;">)</span><span style="color: #002200;">]</span><span style="color: #002200;">)</span>
<span style="color: #002200;">{</span>
<span style="color: #11740a; font-style: italic;">// Soporta multitasking</span>
<span style="color: #002200;">}</span>
<span style="color: #a61390;">else</span>
<span style="color: #002200;">{</span>
<span style="color: #11740a; font-style: italic;">// No soporta multitasking</span>
<span style="color: #002200;">}</span></pre>
</div>
</div>
Si el usuario no soporta la funcionalidad, dependerá de tí decidir que hacer (le muestras un mensaje, no haces nada, le das otra alternativa,...), pero por lo menos garantizas que la app se ejecuta correctamente en todos los iOS y que los usuarios con versiones avanzadas podrán disponer de toda la funcionalidad. Y ya de paso, permites compilar para simulador aunque estés usando librerías de terceros y te ahorras los muchos GBs que ocupa cada SDK :)</div>Angel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]1tag:blogger.com,1999:blog-4861031397020385591.post-84922455121082643822011-03-07T12:48:00.001+01:002012-05-19T22:06:51.446+02:00¿Cómo afectará a los desarrolladores el nuevo dispositivo de Apple?<div dir="ltr" style="text-align: left;" trbidi="on">
<b style="font-size: 21px;">Este post se ha movido de forma permanente a <a href="http://angelolloqui.com/blog/11--C-mo-afectar-a-los-desarrolladores-el-nuevo-dispositivo-de-Apple-">http://angelolloqui.com/blog/11--C-mo-afectar-a-los-desarrolladores-el-nuevo-dispositivo-de-Apple-</a></b><br />
<b style="font-size: 21px;"><br /></b><br />
Hoy me han pedido en el trabajo que escribiese sobre esto. Como ya está hecho el trabajo, aprovecho y dejo por aquí el link al post:<br />
<br />
<a href="http://www.mobivery.com/blog/general/%C2%BFcomo-afecta-el-ipad2-a-los-desarrolladores-analizamos-el-nuevo-dispositivo-de-apple/">http://www.mobivery.com/blog/general/%C2%BFcomo-afecta-el-ipad2-a-los-desarrolladores-analizamos-el-nuevo-dispositivo-de-apple/</a></div>Angel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]0tag:blogger.com,1999:blog-4861031397020385591.post-59679191264219754252010-11-06T18:03:00.012+01:002012-05-19T22:04:51.862+02:00Facebook Like Button on iOS<div dir="ltr" style="text-align: left;" trbidi="on">
<span style="font-size: 21px;"><b>This post has been permanently moved to <a href="http://angelolloqui.com/blog/10-Facebook-Like-Button-on-iOS">http://angelolloqui.com/blog/10-Facebook-Like-Button-on-iOS</a><span id="goog_1972008471"></span><span id="goog_1972008472"></span><a href="http://www.blogger.com/"></a></b></span><br />
<span style="font-size: 130%; font-weight: bold;"><br /></span><br />
<span style="font-size: 130%; font-weight: bold;">The problem</span><br />
<br />
Some days ago a client asked us for including a Like Facebook button in one of his iPad applications. We have previously used Facebook iOS SDK (<a href="https://github.com/facebook/facebook-ios-sdk">https://github.com/facebook/facebook-ios-sdk</a>) for including things like the user's profile photo, friends, and so on so we were pretty sure that this button would be easy to implement.<br />
Upppssss, what an error! Facebook iOS API doesn't include a FB Like button, and the Rest API either. The only way that Facebook seems to give to developers is a HTML button or iframe, both of them thinked for being in a web enviroment. Of course we have the chance to include a webview in the iPad app to include this button, but we should take care of the login process and some other issues, so I did some research and I found this:<br />
<a name='more'></a><br />
<a href="http://petersteinberger.com/2010/06/add-facebook-like-button-with-facebook-connect-iphone-sdk/">http://petersteinberger.com/2010/06/add-facebook-like-button-with-facebook-connect-iphone-sdk/</a><br />
<br />
This web has the solution that I was looking for, but, after including it's code (with a minor change due to a miss method), it didn't work as expected. The FB login dialog opens and then immediatly closes.<br />
<br />
<span style="font-size: 130%; font-weight: bold;">The solution</span><br />
Here are the changes and improvements I have done for resolving it, with the complete code:<br />
<br />
First, we have to customize a FBDialog for taking care of the login process:<br />
<blockquote>
<br />
//FBCustomLoginDialog.h<br />
@interface FBCustomLoginDialog : FBDialog {<br />
}<br />
@end</blockquote>
<br />
<br />
<blockquote>
<br />
//FBCustomLoginDialog.m<br />
@implementation FBCustomLoginDialog<br />
<br />
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request<br />
navigationType:(UIWebViewNavigationType)navigationType {<br />
NSURL* url = request.URL;<br />
if ([[url absoluteString] rangeOfString:@"login"].location==NSNotFound) {<br />
[self dialogDidSucceed:url];<br />
return NO;<br />
}else if (url!=nil){<br />
[_spinner startAnimating];<br />
[_spinner setHidden:NO];<br />
return YES;<br />
}<br />
return NO;<br />
}<br />
@end<br />
<br /></blockquote>
<br />
As you may see, I changed the method "containsString" by "rangeOfString" because the first doesn't exists on iOS SDK. Another minor change I made is that I included a few sentences for start the spinner animation after the user submits the login form, and I had to put and extra "if" because sometimes the URL is null and it can't be loaded.<br />
<br />
OK, so now we have a customized login view, but we need to make the button view for the "I like" UI. In order to build this view, my first option was to inherit my view directly from UIWebView, but after a while I decided to use a UIView over it because it gives me the chance to disable the webview scroll (the webpage generated for the FB Like button usually is heigher that the view itself, resulting in an awful scroll).<br />
I also made some additions for customizing a little the colors showed in the webview, but I found a problem because the CSS of the page are loaded asynchronously with AJAX, so I can't know the exact moment in which I have to inject my javascript that changes the colors. This may be improved overriden the AJAX onreadystate to do it at the exact moment, but I didn't have so much time to investigate on this to only change a color, so I finally took the easy way, injecting the javascript a while after the page loads (3 secs in the code below). If you find a better solution for this I would appreciate if you post your code :)<br />
<br />
Anyway, this is my final code:<br />
<br />
<blockquote>
<br />
//FBLikeButton.h<br />
#define FB_LIKE_BUTTON_LOGIN_NOTIFICATION @"FBLikeLoginNotification"<br />
<br />
typedef enum {<br />
FBLikeButtonStyleStandard,<br />
FBLikeButtonStyleButtonCount,<br />
FBLikeButtonStyleBoxCount<br />
} FBLikeButtonStyle;<br />
<br />
typedef enum {<br />
FBLikeButtonColorLight,<br />
FBLikeButtonColorDark<br />
} FBLikeButtonColor;<br />
<br />
@interface FBLikeButton : UIView<uiwebviewdelegate,> {<br /><br /> UIWebView *webView_;<br /><br /> UIColor *textColor_;<br /> UIColor *linkColor_;<br /> UIColor *buttonColor_;<br />}<br /><br />@property(retain) UIColor *textColor;<br />@property(retain) UIColor *linkColor;<br />@property(retain) UIColor *buttonColor;<br /><br />- (id)initWithFrame:(CGRect)frame andUrl:(NSString *)likePage andStyle:(FBLikeButtonStyle)style andColor:(FBLikeButtonColor)color;<br />- (id)initWithFrame:(CGRect)frame andUrl:(NSString *)likePage;<br /><br />@end</uiwebviewdelegate,></blockquote>
<br />
<br />
<blockquote>
<br />
//FBLikeButton.m<br />
//LoginDialog es estatica para abrir unicamente un login en toda la app<br />
static FBDialog *loginDialog_;<br />
<br />
@implementation FBLikeButton<br />
<br />
@synthesize textColor=textColor_, buttonColor=buttonColor_, linkColor=linkColor_;<br />
<br />
- (id)initWithFrame:(CGRect)frame andUrl:(NSString *)likePage andStyle:(FBLikeButtonStyle)style andColor:(FBLikeButtonColor)color{<br />
if ((self = [super initWithFrame:frame])) { <br />
NSString *styleQuery=(style==FBLikeButtonStyleButtonCount? @"button_count" : (style==FBLikeButtonStyleBoxCount? @"box_count" : @"standard"));<br />
NSString *colorQuery=(color==FBLikeButtonColorDark? @"dark" : @"light");<br />
<br />
NSString *url =[NSString stringWithFormat:@"http://www.facebook.com/plugins/like.php?layout=%@&show_faces=true&width=%d&height=%d&action=like&colorscheme=%@&href=%@",<br />
styleQuery, (int) frame.size.width, (int) frame.size.height, colorQuery, likePage];<br />
<br />
//Creamos una webview muy alta para evitar el scroll interno por la foto del usuario y otras cosas<br />
webView_ = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, 300)];<br />
[self addSubview:webView_];<br />
[webView_ loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];<br />
webView_.opaque = NO;<br />
webView_.backgroundColor = [UIColor clearColor];<br />
webView_.delegate = self;<br />
webView_.autoresizingMask = UIViewAutoresizingFlexibleWidth;<br />
[[webView_ scrollView] setBounces:NO];<br />
self.backgroundColor=[UIColor clearColor];<br />
self.clipsToBounds=YES;<br />
<br />
[[NSNotificationCenter defaultCenter] addObserver:webView_ selector:@selector(reload) name:FB_LIKE_BUTTON_LOGIN_NOTIFICATION object:nil];<br />
<br />
}<br />
return self;<br />
}<br />
<br />
- (id)initWithFrame:(CGRect)frame andUrl:(NSString *)likePage{<br />
return [self initWithFrame:frame andUrl:likePage andStyle:FBLikeButtonStyleStandard andColor:FBLikeButtonColorLight];<br />
}<br />
<br />
- (void)dealloc {<br />
<br />
[[NSNotificationCenter defaultCenter] removeObserver:webView_ name:FB_LIKE_BUTTON_LOGIN_NOTIFICATION object:nil];<br />
<br />
[webView_ stopLoading];<br />
webView_.delegate=nil;<br />
[webView_ removeFromSuperview];<br />
[webView_ release]; webView_=nil;<br />
<br />
self.linkColor=nil;<br />
self.textColor=nil;<br />
self.buttonColor=nil;<br />
<br />
[super dealloc];<br />
}<br />
<br />
<br />
- (void) configureTextColors{<br />
NSString *textColor=[textColor_ hexStringFromColor];<br />
NSString *buttonColor=[buttonColor_ hexStringFromColor];<br />
NSString *linkColor=[linkColor_ hexStringFromColor];<br />
<br />
NSString *javascriptLinks = [NSString stringWithFormat:@"{"<br />
"var textlinks=document.getElementsByTagName('a');"<br />
"for(l in textlinks) { textlinks[l].style.color='#%@';}"<br />
"}", linkColor];<br />
<br />
NSString *javascriptSpans = [NSString stringWithFormat:@"{"<br />
"var spans=document.getElementsByTagName('span');"<br />
"for(s in spans) { if (spans[s].className!='liketext') { spans[s].style.color='#%@'; } else {spans[s].style.color='#%@';}}"<br />
"}", textColor, (buttonColor==nil? textColor : buttonColor)];<br />
<br />
//Lanzamos el javascript inmediatamente<br />
if (linkColor)<br />
[webView_ stringByEvaluatingJavaScriptFromString:javascriptLinks];<br />
if (textColor)<br />
[webView_ stringByEvaluatingJavaScriptFromString:javascriptSpans];<br />
<br />
//Programamos la ejecucion para cuando termine<br />
if (linkColor)<br />
[webView_ stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setTimeout(function () %@, 3000)", javascriptLinks]];<br />
if (textColor)<br />
[webView_ stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setTimeout(function () %@, 3000)", javascriptSpans]];<br />
<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////////////////////////<br />
#pragma mark -<br />
#pragma mark UIWebViewDelegate<br />
<br />
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {<br />
<br />
if (loginDialog_!=nil)<br />
return NO;<br />
<br />
// if user has to log in, open a new (modal) window<br />
if ([[[request URL] absoluteString] rangeOfString:@"login.php"].location!=NSNotFound){ <br />
loginDialog_= [[[FBCustomLoginDialog alloc] init] autorelease];<br />
[loginDialog_ loadURL:[[request URL] absoluteString] get:nil]; <br />
loginDialog_.delegate = self;<br />
[loginDialog_ show];<br />
[loginDialog_.delegate retain]; //Retenemos el boton que ha abierto el login para que pueda recibir la confirmacion correctamente<br />
return NO;<br />
}<br />
if (([[[request URL] absoluteString] rangeOfString:@"/connect/"].location!=NSNotFound) || ([[[request URL] absoluteString] rangeOfString:@"like.php"].location!=NSNotFound)){ <br />
return YES;<br />
}<br />
<br />
NSLog(@"URL de Facebook no contemplada: %@", [[request URL] absoluteString]);<br />
<br />
return NO;<br />
}<br />
<br />
- (void)webViewDidFinishLoad:(UIWebView *)webView{<br />
<br />
[self configureTextColors];<br />
}<br />
<br />
///////////////////////////////////////////////////////////////////////////////////////////////////<br />
#pragma mark -<br />
#pragma mark Facebook Connect<br />
<br />
- (void)dialogDidSucceed:(FBDialog*)dialog {<br />
[loginDialog_.delegate release];<br />
loginDialog_.delegate=nil;<br />
loginDialog_=nil;<br />
<br />
//Lanzamos la notificacion para que se actualicen los botones<br />
[[NSNotificationCenter defaultCenter] postNotificationName:FB_LIKE_BUTTON_LOGIN_NOTIFICATION object:nil];<br />
}<br />
<br />
<br />
/**<br />
* Called when the dialog succeeds and is about to be dismissed.<br />
*/<br />
- (void)dialogDidComplete:(FBDialog *)dialog{<br />
[self dialogDidSucceed:dialog];<br />
}<br />
<br />
/**<br />
* Called when the dialog succeeds with a returning url.<br />
*/<br />
- (void)dialogCompleteWithUrl:(NSURL *)url{<br />
[self dialogDidSucceed:loginDialog_];<br />
}<br />
<br />
/**<br />
* Called when the dialog get canceled by the user.<br />
*/<br />
- (void)dialogDidNotCompleteWithUrl:(NSURL *)url{<br />
[self dialogDidSucceed:loginDialog_];<br />
}<br />
<br />
/**<br />
* Called when the dialog is cancelled and is about to be dismissed.<br />
*/<br />
- (void)dialogDidNotComplete:(FBDialog *)dialog{<br />
[self dialogDidSucceed:loginDialog_];<br />
}<br />
<br />
/**<br />
* Called when dialog failed to load due to an error.<br />
*/<br />
- (void)dialog:(FBDialog*)dialog didFailWithError:(NSError *)error{<br />
[self dialogDidSucceed:loginDialog_];<br />
}<br />
<br />
@end</blockquote>
<br />
<br />
You may notice that I added a lot of code and methods to the original example. Almost all of them are for customizing the UI and make a proper use of our customized login dialog (the FBDialogDelegate methods).<br />
The only important changes are the use of the NSNotificationCenter for sending notifications when the dialog success (and so every existing Like button could refresh with the new credentials) and the static reference to the FBCustomDialog to avoid multiple login popups at the same time.<br />
<br />
The UIColor extensions used on the code above can be found here:<br />
<a href="http://arstechnica.com/apple/guides/2009/02/iphone-development-accessing-uicolor-components.ars">http://arstechnica.com/apple/guides/2009/02/iphone-development-accessing-uicolor-components.ars</a><br />
<br />
Finally, you would see a method that doesn't exists on a standard UIWebView:<br />
<blockquote>
[[webView_ scrollView] setBounces:NO];</blockquote>
<br />
this method is a little hack I use sometimes to disable the scroll or bounces of the webview, but it may fail on future iOS versions. Anyway, if you still want to use it, this is what it does (I have it in a UIWebView category)<br />
<blockquote>
<br />
- (UIScrollView *) scrollView{<br />
NSArray *subviews = [self subviews];<br />
for (UIView *view in subviews){<br />
if ([view isKindOfClass:[UIScrollView class]])<br />
return (UIScrollView *) view;<br />
}<br />
return nil;<br />
}</blockquote>
<br />
<br />
<br />
And thats all! with these 2 classes you are able to include your FB Like buttons in your views in an easy way, just with something like the following:<br />
<blockquote>
<br />
FBLikeButton *likeButton = [[FBLikeButton alloc] initWithFrame:frame andUrl:@"www.mylikeurl.com"];<br />
[view addSubview:likeButton];<br />
[likeButton release];</blockquote>
<br />
or even with customized colors:<br />
<blockquote>
<br />
FBLikeButton *likeButton = [[FBLikeButton alloc] initWithFrame:frame andUrl:@"www.mylikeurl.com"];<br />
[likeButton setTextColor:COLOR_DARK_GRAY];<br />
[likeButton setLinkColor:COLOR_CLEAR_GRAY];<br />
[view addSubview:likeButton];<br />
[likeButton release];</blockquote>
<br />
<br />
I hope it helps you all!<br />
<br />
<br />
<span style="font-size: 130%; font-weight: bold;">Known issues</span><br />
<ul>
<li>Login Dialog has a double title bar, the FBDialog title and the FB webpage title. I don't think there is an easy solution for this, but I consider it as a minor issue.</li>
<li>FBCustomLoginDialog and FBLoginDialog uses different login methods, so the user have to make login twice if you use the FB iOS SDK for other staff. I don't think there is a workaround for this issue.</li>
<li>UI colors may change a little time after the button loads, as commented before, and it may stop working on the future if FB changes it's webpage structure.</li>
<li>There is no easy way for customizing layout or even alignment of the elements.</li>
<li>UIWebview have a little render delay as it loads remote content.</li>
</ul>
</div>Angel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]12tag:blogger.com,1999:blog-4861031397020385591.post-65195003473693858242010-07-16T01:01:00.003+02:002012-05-19T22:07:25.106+02:00Las ventajas de nil en Objective-C<div dir="ltr" style="text-align: left;" trbidi="on">
<b style="font-size: 21px;">Este post se ha movido de forma permanente a <a href="http://angelolloqui.com/blog/9-Las-ventajas-de-nil-en-Objective-C">http://angelolloqui.com/blog/9-Las-ventajas-de-nil-en-Objective-C</a></b><br />
<br />
Para los que venimos de lenguajes como Java o C++ donde un objeto null es un peligro, cuando llegamos a Objective-C descubrimos la gran ventaja que tenemos en este lenguaje.<br />
<br />
En objective-C, un puntero apuntando a nil es un objeto sobre el que se pueden invocar métodos.<br />
<br />
Es decir, cualquiera de las siguientes líneas son perfectamente válidas:<br />
<br />
<span style="font-family: 'courier new';">id objeto=nil;</span><br />
<span style="font-family: 'courier new';">[objeto metodo];</span><br />
<span style="font-family: 'courier new';">[nil metodo];</span><br />
<br />
<a name='more'></a><br />
<br />
Quizá te preguntes que pasa si se ejecuta un método sobre nil? pues nada, cualquier ejecución sobre nil simplemente devuelve a su vez un nil. Por ejemplo, en este código:<br />
<br />
<span style="font-family: 'courier new';">id objeto1= [nil metodo];</span><br />
<span style="font-family: 'courier new';">id objeto2 = [objeto1 metodo];</span><br />
<br />
En ambos casos, objeto1 y objeto2 serán nil a su vez, pero ningún error habrá ocurrido (en vez de los "Null pointer exception" que tendríamos en Java).<br />
<br />
<br />
Quizá pienses que esto tampoco aporta nada, ya que simplemente con hacer una comprobación de si objeto1 es nil antes de invocar nada sobre él sería suficiente, pero lo cierto es que se convierte en algo muy práctico, cómodo y sobre todo robusto frente a errores. No significa que nunca tengas que comprobar si algo es nil, ya que es probable que en muchos casos tengas que hacer una ejecución diferente en un caso o en el otro, pero sí ahorra muchas comprobaciones innecesarias y por tanto código, además de reducir los errores por un "descuido de comprobación de null".<br />
<br />
Un ejemplo muy claro de este buen uso es en las properties y en los deallocs. Por ejemplo, un dealloc típico podría ser algo como:<br />
<br />
<br />
<span style="font-family: 'courier new';">- (void)dealloc {</span><br />
<span style="font-family: 'courier new';"> [titleLabel_ release]; titleLabel_=nil;</span><br />
<span style="font-family: 'courier new';"> [subtitleLabel_ release]; subtitleLabel_=nil;</span><br />
<span style="font-family: 'courier new';"> [introLabel_ release]; introLabel_=nil;</span><br />
<span style="font-family: 'courier new';"> [authorLabel_ release]; authorLabel_=nil;</span><br />
<span style="font-family: 'courier new';"> [dateLabel_ release]; dateLabel_=nil;</span><br />
<span style="font-family: 'courier new';"> [commentsLabel_ release]; commentsLabel_=nil;</span><br />
<span style="font-family: 'courier new';"> [textLabels_ release]; textLabels_=nil;</span><br />
<span style="font-family: 'courier new';"> [contentScroll_ release]; contentScroll_=nil;</span><br />
<span style="font-family: 'courier new';"> [multimediaView_ release]; multimediaView_=nil;</span><br />
<span style="font-family: 'courier new';"> [remoteImageView_ release]; remoteImageView_=nil;</span><br />
<span style="font-family: 'courier new';"> [multimediaFooterLabel_ release]; multimediaFooterLabel_=nil;</span><br />
<span style="font-family: 'courier new';"> [twitterAlertViewController_ release]; twitterAlertViewController_=nil;</span><br />
<span style="font-family: 'courier new';"> [bannerView_ release]; bannerView_=nil;</span><br />
<br />
<span style="font-family: 'courier new';"> [super dealloc]; </span><br />
<span style="font-family: 'courier new';">}</span><br />
<br />
<br />
<br />
<br />
Imaginate la cantidad de líneas extras que llevaría el dealloc anterior si tenemos que comprobar cada variable antes de liberarla para evitar provocar un "null pointer exception" sobre variables que no estén creadas!<br />
Por cierto, puedes ver que después de cada release hago una igualación a nil. Esto no es obligatorio pero sí es una muy buena práctica porque si este release se hace en otro punto es bueno que el puntero lo apuntemos a nil para que no de errores en un hipotético uso posterior.<br />
<br />
<br />
<span style="font-weight: bold;">Consideraciones</span><br />
<br />
A pesar de que la ejecución de métodos sobre nil sea correcta, no significa que lo sea por fuerza en los parámetros de un método. Por ejemplo, en un array no podemos agregar un nil como objeto ya que lanza un error:<br />
<br />
<span style="font-family: 'courier new';">[array addObject:nil]; // Error</span><br />
<br />
Por esto, aunque podemos relajarnos con las comprobaciones a nil al invocar métodos, hay que tener cuidado cuando se usa como parámetro de un método de otro objeto. El nil es mucho mejor que el null de Java, pero no perfecto ;)</div>Angel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]0tag:blogger.com,1999:blog-4861031397020385591.post-85759434522467730262010-07-07T00:34:00.005+02:002012-05-19T22:07:59.117+02:00Gestión de memoria en iPhone SDK<div dir="ltr" style="text-align: left;" trbidi="on">
<b style="font-size: 21px;">Este post se ha movido de forma permanente a <a href="http://angelolloqui.com/blog/8-Gesti-n-de-memoria-en-iPhone-SDK">http://angelolloqui.com/blog/8-Gesti-n-de-memoria-en-iPhone-SDK</a></b><br />
<b style="font-size: 21px;"><br /></b><br />
El desarrollo de aplicaciones para iPhone o iPad, a diferencia de Android y otras plataformas, tiene un tema bastante espinoso y que a la gente le suele parecer complicado al principio: la gestión de memoria. Debido a que la aplicación es compilada a código nativo, sin máquinas virtuales de por medio, no contamos con un recolector de basura que nos haga la limpieza de memoria de las variables en desuso. En esta entrada trataré de exponer las convenciones y consideraciones que debes tener en cuenta al enfrentarte a este tipo de desarrollos.<br />
<br />
<a name='more'></a><br />
Como decía anteriormente, lo más importante es saber que aunque en Objective-C puede existir un recolector de basura cuando programamos para MacOs, no existe en iPhone o iPad, por lo que toda la gestión es manual. ¿Qué significa manual? pues que toda memoria que quieras utilizar tienes que crearla (normalmente mediante alloc), retenerla si es necesario (lo veremos después), y liberarla cuando dejes de necesitarla (con release). Esta forma de trabajar es chocante si vienes de lenguajes donde no sea necesario, tales como los interpretados (Java, .Net,...) o los de scripting (ruby, php,...).<br />
<br />
Si no hacemos la gestión de memoria de forma correcta pueden ocurrir dos cosas:<br />
<br />
<span style="font-weight: bold;">1.- Se queda memoria sin liberar</span>: el iPhone emepezará a quedarse sin memoria con cada uso de la aplicación, y será necesario reiniciar el dispositivo pasados unos cuantos usos según la cantidad de memoria dejada. A este tipo de lagunas de memoria los llamamos "leaks".<br />
<span style="font-weight: bold;">2.- Se libera la memoria antes de tiempo</span>: cuando el código trate de acceder a un área de memoria que ya ha sido liberada, la aplicación lanzará una excepción y se cerrará.<br />
<br />
Como veis, ambas cosas con muy preocupantes y hay que evitarlas por todos los medios.<br />
<br />
Para facilitarnos la vida, Apple nos ha aportado un mecanismo de "reference counting" en el objeto NSObject y ha creado dos convenciones que deberás seguir si quieres hacer esta gestión de memoria de forma correcta, y que te ayudarán a determinar qué clase debe hacer que retain/release y en que momento.<br />
<br />
Pero expliquemos por partes:<br />
<br />
<span style="font-weight: bold;">Reference counting.</span><br />
<br />
El reference counting es un mecanismo por el cuál los objetos llevan algo parecido a un contador de punteros que le apuntan. De esta forma, si un objeto está apuntado por varios otros, no podemos liberarlo hasta que deje de ser apuntado por el resto. La diferencia con otros entornos con Garbage Collector es que en Objective-C este conteo se realiza de forma manual, realizándose más o menos de esta forma:<br />
R.1.- Cuando reservamos un objeto mediante su método "alloc" o "copy", el nuevo objeto (llamémosle MiObjeto) pasa a tener un contador de +1.<br />
R.2.- A partir de este momento, cuando otro objeto apunta a MiObjeto debe indicarselo mediante la invocación del método "retain". El método "retain" sumará un +1 al contador de referencias de MiObjeto.<br />
R.3.- Cuando otro objeto deja de apuntar a MiObjeto, se invoca el método "release". El método "release" decrementará en 1 el contador de referencias. Si MiObjeto pasa a tener un contador de 0 se invoca el método "dealloc", que es donde se realiza la liberación.<br />
<br />
Como podeis ver, de esta forma no invocamos directamente los métodos de liberación de memoria (dealloc), sino que lo que hacemos es invocar siempre el método "release" para permitir que, si otros objetos tienen retenido el que queremos liberar, se destruya la memoria y se produzca el fallo 2.- del primer párrafo. Esta liberación ocurrirá cuando los otros objetos realicen a su vez el release de MiObjeto (ojo, como queda claro, cada retain y alloc debe ejecutar en algún momento un release, dado que de otra forma tendremos un leak como se indica en 1.-)<br />
<br />
Ejemplo de reference counting:<br />
<span style="font-family: verdana;"></span><br />
<span style="font-family: verdana;">for (int i=0; i<1000;i++){</span><br />
<span style="font-family: verdana;"> //Creamos memoria</span><br />
<span style="font-family: verdana;"> id objeto =[ [ClaseCreadora alloc] init];</span><br />
<span style="font-family: verdana;"> //Pasamos el objeto a otra clase</span><br />
<span style="font-family: verdana;"> [ClaseB nuevoObjeto:objeto];</span><br />
<span style="font-family: verdana;"> //Liberamos la memoria</span><br />
<span style="font-family: verdana;"> [objeto release];</span><br />
<span style="font-family: verdana;">}</span><br />
<br />
En este ejemplo vemos cómo se ha hecho un <span style="font-family: verdana;">release</span> por cada <span style="font-family: verdana;">alloc</span>, y se puede suponer que <span style="font-family: verdana;">ClaseB</span> estará haciendo un <span style="font-family: verdana;">retain</span> sobre <span style="font-family: verdana;">objeto</span> si lo necesita utilizar más tarde.<br />
<br />
<span style="font-weight: bold;">Convención ownership</span><br />
<br />
El reference counting nos ayuda a evitar gran cantidad de problemas surgidos al compartir memoria entre diferentes clases y objetos, pero plantea problemas a la hora de decidir dónde y quién debe hacer los releases. Por ejemplo, sin un método tiene que devolver un objeto nuevo, y no guarda ningún puntero al mismo, ¿cómo libera la memoria de este nuevo objeto? como no guarda el puntero no puede hacer el release más tarde, tampoco lo puede ejecutar inmediatamente porque si lo hace se liberará la memoria en este mismo momento y producirá el fallo 2.- al intentar usarla, y tampoco es buena idea devolverlo sin el release porque en ese caso se producirá un leak si la otra clase no lo libera. Aquí entra en juego la convención Ownership.<br />
<br />
Según esta convención, todo objeto es responsable de liberar la memoria que crea o retiene, pero aporta un par de reglas extras para solucionar el problema anterior:<br />
<br />
O.1.- Cuando un objeto va a utilizar memoria creada por otro método debe hacer un retain para evitar su liberación prematura.<br />
O.2.- Todo objeto debe liberar la memoria creada (mediante métodos alloc, copy o new) y retenida (mediante método retain), invocando el método release en algún momento en su ciclo de vida. Por cada alloc/retain ejecutado debe hacerse un release.<br />
O.3.- Si un método devuelve un objeto que no va a liberar, el método deberá llamarse allocXXX, copyXXX o newXXX (donde XXX se sustituye por el nombre que elijas). De esta forma, el que invoca el método puede conocer esta característica y, aplicando la regla O.2.- podrá liberarla. Esta misma regla podría aplicarse N veces si cada método devuelve a su vez el objeto nuevo a su padre.<br />
<br />
Ejemplo convención Ownership:<br />
<br />
<span style="font-family: verdana;">- (Clase *) newObjectWithData:(NSData *)data{<br />//Creamos el objeto<br />Clase *object = [[Clase alloc] init];<br />//Ejecutamos lo que sea<br />[object setData:data];<br />//Devolvemos el objeto<br />return object;<br />}</span>En este ejemplo podemos hacer la devolución gracias a haber nombrado al método <span style="font-family: verdana;">newXXX</span>, que indicará al que lo invoque que el objeto de tipo <span style="font-family: verdana;">Clase *</span> devuelto está sin liberar.<br />
<br />
<span style="font-weight: bold;">Autorelease</span><br />
<br />
Por último, tenemos un último recurso que nos ayudará a gestionar la memoria. Lo he dejado al final porque es el poco óptimo y propenso a erores, por lo que deberías tratar de evitarlo donde sea posible. Se trata del método autorelease, que nos permite indicar que una variable debe ejecutar un release pero más tarde. ¿Cuándo? pues no lo sabes a ciencia cierta porque dependerá de la pila de llamadas, pero el autorelease funciona así:<br />
<br />
A.1.- Un método ejecuta un autorelease sobre un objeto (MiObjeto) que tenía retenido (retain) o que había creado previamente (alloc/copy).<br />
A.2.- El método autorelease busca la NSAutoreleasePool en la pila de llamadas más cercana y programa la ejecución del release sobre este objeto<br />
A.3.- La ejecución de la pila de llamadas termina, se ejecuta el NSAutoreleasePool release y se realizan todos los releases programados (se realiza el release sobre MiObjeto).<br />
<br />
Así, durante todo el espacio entre A.1 y A.3 la variable seguirá retenida, pero no se tratará de un leak aunque se pierda el puntero ya que este release está programado en la AutoreleasePool. El espacio entre A.1 y A.3 normalmente será la pila de llamdas del sistema (por ejemplo, si se pulsa un botón en el interfaz gráfico se crea una pila y se destruye cuando se termina de procesar el evento de pulsación), pero pueden crearse pilas dentro de otras para hacer las liberaciones de una forma más frecuente.<br />
<br />
Ejemplo devolución con autorelease:<br />
<br />
<span style="font-family: verdana;">- (Clase *) objectWithData:(NSData *)data{<br />//Creamos el objeto<br />Clase *object = [[Clase alloc] init];<br />//Ejecutamos lo que sea<br />[object setData:data];<br />//Devolvemos el objeto<br />return [object autorelease];<br />}<br /></span>En este ejemplo a diferencia del utilizado en el patrón ownership vemos que hemos ejecutado un <span style="font-family: verdana;">autorelease</span> sobre el <span style="font-family: verdana;">objet</span>, por lo que el método no deberá llamarse <span style="font-family: verdana;">newXXX</span> porque el padre no tiene que preocuparse de la gestión de memoria de dicho <span style="font-family: verdana;">object</span> (el release no se ha ejecutado todavía pero lo hará más tarde).<br />
<br />
Ejemplo de uso de NSAutoreleasePool:<br />
<br />
<span style="font-family: verdana;">//Creamos una nueva pool</span><br />
<span style="font-family: verdana;">NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] ini];</span><br />
<span style="font-family: verdana;">for (int i=0; i<1000;i++){</span><br />
<span style="font-family: verdana;"> //Creamos memoria</span><br />
<span style="font-family: verdana;"> id objeto =[ [ClaseCreadora alloc] init];</span><br />
<span style="font-family: verdana;"> //Pasamos el objeto a otra clase</span><br />
<span style="font-family: verdana;"> [ClaseB nuevoObjeto:objeto];</span><br />
<span style="font-family: verdana;"> //Programamos el autorelease para hacerlo más tarde</span><br />
<span style="font-family: verdana;"> [objeto autorelease];</span><br />
<span style="font-family: verdana;">}</span><br />
<span style="font-family: verdana;">//Liberamos toda la memoria con autorelease desde la creación de la pool</span><br />
<span style="font-family: verdana;">[pool release];</span><br />
<br />
<br />
<br />
<span style="font-weight: bold;">Resumen</span><br />
<br />
La gestión de memoria es una tarea que al principio parece compleja, pero una vez comprendidas las normas y el funcionamiento acaba siendo un tema bastante más sencillo de lo que es en otros lenguajes sin reference counting o patrones ownership. Al final, normalmente se resumen en:<br />
<br />
a. Toda memoria creada en tu clase con métodos alloc, copy o new debe llevar un release o autorelease en su ciclo de vida<br />
<br />
b. Toda memoria que uses creada por otra clase deberías protegerla con retain y liberarla cuando no sea necesaria (idem a punto a.)<br />
<br />
c. Si un método devuelve memoria sin liberar debe llamarse newXXX, allocXXX o copyXXX<br />
<br />
d. Puedes ejecutar autorelease si necesitas posponer la ejecución de un release, pero no puedes saber el instante exacto en el que se realizará a priori (por ello, debes seguir b. si se usa esa memoria también a posteriori desde otro punto)<br />
<br />
e. Los métodos y clases de Apple cumplen los apartados anteriores.</div>Angel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]0tag:blogger.com,1999:blog-4861031397020385591.post-8705540428613678932010-07-06T23:50:00.000+02:002012-05-19T22:08:49.383+02:00Liberación de memoria en IBOutlets<div dir="ltr" style="text-align: left;" trbidi="on">
<b style="font-size: 21px;">Este post se ha movido de forma permanente a <a href="http://angelolloqui.com/blog/7-Liberaci-n-de-memoria-en-IBOutlets">http://angelolloqui.com/blog/7-Liberaci-n-de-memoria-en-IBOutlets</a></b><br />
<b style="font-size: 21px;"><br /></b><br />
Hoy voy me he encontrado con un problema en el trabajo relacionado con la liberación de memoria y la convención Ownership. Bajo esta convención, las clases solo son responsables de liberar aquella memoria que reservan directamente (mediante alloc, retain o copy), pero resulta que no siempre es así.<br />
<br />
<a name='more'></a><br />
El problema surge cuando nos enfrentamos a un proyecto iPhone/iPad donde tenemos parte del código creado de forma gráfica mediante Interface Builder. Al usar IB las variables se enlazan con el código mediante el etiquetado de las mismas con la palabra clave IBOutlet sin necesidad de reservar memoria.<br />
<br />
Es decir, si tenemos un UILabel que querecemos enlazar desde IB, lo que hacemos es declararla de forma:<br />
<br />
<span style="font-size: 100%;"><span style="font-family: verdana;">IBOutlet UILabel *label;</span></span><br />
<br />
y desde Interface Builder conectarla con nuestro label creado gráficamente.<br />
<br />
Hasta aquí todo OK, el problema aparece cuando te enfrentas a la liberación de memoria. Bajo el patrón ownership, como no hemos hecho nosotros la reserva de memoria, no deberíamos hacer nosotros su liberación, ya que de hacerlo podemos estar decrementando en exceso su retain count y acabar liberando antes de tiempo este objeto (con el consiguiente BAD ACCESS). Pues bien, parece que con estas variables IBOutlet el patrón Ownership no se cumple, porque es el desarrollador el responsable de su liberación en el dealloc, y opcionalmente en el viewDidUnload.<br />
<br />
Podeis ver la documentación oficial aquí:<br />
<a href="http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmNibObjects.html">http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmNibObjects.html</a><br />
<br />
<br />
En resumen, las variables declaradas IBOutlet deben ser liberadas explícitamente por el desarrollador, y además Apple recomienda usar una property tipo retain en su declaración y liberarlas también en el viewDidUnload para mejorar la gestión de memoria en situaciones de Memory Warning.</div>Angel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]0tag:blogger.com,1999:blog-4861031397020385591.post-84707338803191612342010-07-02T01:28:00.000+02:002012-05-19T22:09:17.258+02:00Recursos de desarrollo para iPhone<div dir="ltr" style="text-align: left;" trbidi="on">
<b style="font-size: 21px;">Este post se ha movido de forma permanente a <a href="http://angelolloqui.com/blog/6-Recursos-de-desarrollo-para-iPhone">http://angelolloqui.com/blog/6-Recursos-de-desarrollo-para-iPhone</a></b><br />
<b style="font-size: 21px;"><br /></b><br />
Voy a utilizar este post para hacer un recopilatorio de aquellos cursos que sean de interés en el desarrollo para iPhone y iPad. Si teneis buenos enlaces, publicadlos en los comentarios para agregarlos a este listado:<br />
<br />
<a name='more'></a><br />
<br />
<span style="font-weight: bold;">Links útiles</span><br />
<br />
<a href="https://developer.apple.com/iphone">Portal de desarrollo de iPhone</a>: Principal sitio de referencia y punto de partida para gestionar y desarrollar tus aplicaciones. Mételo en favoritos.<br />
<br />
<br />
<span style="font-weight: bold;">Objective-C</span><br />
<br />
<a href="http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html">Introducción a Objective-C oficial</a>: Introducción al lenguaje Objective-C oficial por Apple. Muy buen recurso, imprescindible su lectura antes de lanzarse a desarrollar. Cuidado porque este curso es general de Objective-C, por lo que trata algunos aspectos como el recolector de basura de los que no disponemos en iPhone.<br />
<br />
<a href="http://es.wikipedia.org/wiki/Objective-C">Wikipedia: Objective-C</a>: Breve introducción a Objective-C en la Wikipedia. No tiene mucho valor pero sirve para tener una primera visión del lenguaje.<br />
<br />
<a href="http://www.abcdatos.com/tutoriales/tutorial/z8973.html">Objective-C para programadores Java</a>: Tutorial muy bien montado donde se explican las diferencias entre Java y Objective-C. Altamente recomendado si eres un programador con experiencia en Java.<br />
<br />
<br />
<span style="font-weight: bold;">Cursos y Tutoriales</span><br />
<br />
<a href="http://www.stanford.edu/class/cs193p/cgi-bin/drupal/downloads-2010-winter">Introducción a iPhone por la Universidad de Standford</a>: Muy buen curso de introducción. Empieza con conceptos básicos y acaba con temas muy avanzados como OpenGLEs. Altamente recomendable<br />
<br />
<a href="http://courses.csail.mit.edu/iphonedev/">Introducción a iPhone por el MIT</a>: Buen curso de introducción. Lo renuevan a menudo por lo que es un buen recurso para introducirse, pero no lleva tanta profundidad como el anterior.<br />
<br />
<br />
<span style="font-weight: bold;">Otros</span><br />
<br />
<a href="http://developer.apple.com/iphone/library/documentation/UserExperience/Conceptual/MobileHIG/Introduction/chapter_1_section_1.html">Human Interfaces Guidelines</a>: Recurso de imprescindible lectura si pretendes dedicarte de forma profesional al desarrollo para iPhone. En él se explican los componentes que existen, las normas que deben seguirse en su utilización y aspectos de usabilidad y navegabilidad que deben tener tus aplicaciones.</div>Angel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]0tag:blogger.com,1999:blog-4861031397020385591.post-16539359047304296082010-07-02T01:25:00.000+02:002012-05-19T22:09:47.796+02:00Desarrollar para iPhone/iPad: ¿Qué necesito y por dónde empiezo?<div dir="ltr" style="text-align: left;" trbidi="on">
<b style="font-size: 21px;">Este post se ha movido de forma permanente a <a href="http://angelolloqui.com/blog/5-Desarrollar-para-iPhone-iPad-Qu-necesito-y-por-d-nde-empiezo-">http://angelolloqui.com/blog/5-Desarrollar-para-iPhone-iPad-Qu-necesito-y-por-d-nde-empiezo-</a></b><br />
<b style="font-size: 21px;"><br /></b><br />
Voy a empezar a escribir en este blog sobre iPhone/iPad, y como no puede ser de otra forma, empezaré por el principio: ¿Qué necesito y por dónde empiezo a programar para iPhone/iPad?<br />
<br />
Vamos allá, pero aviso que no entraré en ningún detalle técnico todavía.<br />
<a name='more'></a><br />
<br />
<span style="font-size: 130%; font-weight: bold;">Requisitos previos</span><br />
<br />
<span style="font-weight: bold;">1.- Lo primero es hacerte con un Mac.</span> Existen emuladores con los que podríais llegar a probar el entorno y hacer pequeños pinitos, pero el SDK de iPhone consume bastantes recursos, por lo que es imprescindible que os hagais con un MacOSX. Por cierto, a día de hoy sólo hay soporte para SnowLeopard, por lo que aseguraos que además vuestro Mac está actualizado.<br />
<span style="font-weight: bold;">2.- Crearte una cuenta en en Apple Developer Portal</span> (http://developer.apple.com/iphone). Existen diferentes tipos de cuentas, desde las gratuitas hasta las de pago. Con las gratuitas podrás probar el SDK, pero no podrás subir aplicaciones al AppStore. Mi consejo es que desde el primer momento te des de alta como un desarrollador de pago, ya que el proceso de alta puede tardar algunos días y este tipo de cuentas te dan acceso a todo el material por unos 70€ al año.<br />
<span style="font-weight: bold;">3.- Descargarte el SDK de desarrollo</span> de iPhone de http://developer.apple.com/iphone/. Bájate la versión más actual. En el momento de redactar este post era la 4.0, pero posiblemente las haya más actuales cuando lo leas. Independientemente de la versión, bája e instala el SDK en tu equipo. Ten paciencia porque son más de 2GB. Este SDK incluye, entre alguna otra, 4 grandes herramientas: XCode, Interface Builder, Simulator e Instruments. Ten en cuenta que el SDK de desarrollo para iPhone y iPad es el mismo, por lo que a partir de ahora hablaré de iPhone para referirme a ambos.<br />
<br />
Con esto ya estarías listo para empezar a probar el SDK con los primeros ejemplos y tutoriales en un simulador. No obstante, si tu intención es probar la aplicación en un dispositivo, es imprescindible que te des de alta en el Provisioning Portal y añadas tu dispositivo a un provisioning profile válido. En las siguientes líneas te explico como hacerlo (sáltatelas si no te intersa probar sobre dispositivos reales):<br />
<br />
<span style="font-weight: bold;">4.-</span> Logueate en el Developer Portal y accede a iPhone Provisioning Portal.<br />
<span style="font-weight: bold;">5.- Crear un certificado (de development)</span>: Entra en Certificates y sigue los pasos para crear un certificado nuevo. A grandes rasgos, lo que hay que hacer aquí simplemente es crear un nuevo certificado con el Keychain (Acceso a Llaveros en español) y subirlo y validarlo en el portal. Te aconsejo además que entres en tu Keychain y exportes la clave privada del certificado en un p12 y la guardes en un lugar seguro, porque si un día necesitas utilizar el certificado en otro equipo te hará falta.<br />
<span style="font-weight: bold;">6.- Dar de alta tus dispositivos</span>: Entra en la sección devices y añade nuevos dispositivos con el UDID. Este UDID lo puedes obtener utilizando el iTunes o mediante el Organizer (incluido en el Xcode, dentro de herramientas). Si utilizas el iTunes, conecta tu dispositivo (no es necesario sincronizar) y accede a él desde el menu lateral izquierdo. En la pestaña de resumen verás el UDID (puede aparecer "Número de serie" en su lugar, si es así simplemente pincha sobre él para que cambie y te muestre el UDID). Cópialo (comando+c funciona, no lo hagas a mano!) y agrégalo en el portal junto con un nombre asociado.<br />
Cuidado porque el portal limita a 100 el número de dispositivos que se pueden dar de alta, y NO se pueden borrar, así que mucho cuidado con utilizarlo indiscriminadamente porque pueden no ser tantos.<br />
<span style="font-weight: bold;">7.- Crear un appID</span>: Un AppId no es una aplicación en sí misma, sino un identificador para una o más aplicaciones. Se utiliza para discriminar entre provisioning profiles y son fundamentales tenerlos bien configurados para temas más avanzados como APNS o InAppPurchase. De momento, con un AppId genérico será suficiente. Para crearlo, simplemente asignale un nombre ("all apps" por ejemplo) y pon como bundle identifier un *. El * es un comodin que nos permitirá referirnos a cualquier aplicación usando este AppId.<br />
<span style="font-weight: bold;">8.- Crear un provisioning profile (de development)</span>: Entra en provisioning y crea uno nuevo. Te pedirá asignarle un nombre, seleccionar un AppId y una lista de dispositivos. Mi consejo para el nombre es que independientemente de cuál elijas lleve la palabra "Development" y algo que te permita reconocer que es el provisioning genérico para todas las apps, ya que es posible que te juntes con muchos provisioning en el futuro si vas subir la aplicación al AppStore, tienes diferentes cuentas o utilizas temas avanzados como comentaba anteriormente. Por ejemplo, un buen nombre podría ser algo como "AngelAllAppsDevelopmentProfile". Con esto hecho, solo te falta descargar el profile que acabas de crear y instalarlo (basta con hacer doble click sobre él, o moverlo a la carpeta $user/Library/MobileDevice/Provisioning Profiles)<br />
<br />
Antes de terminar con esta parte de los certificados, voy a explicar brevemente para que se usan:<br />
Los provisioning profiles son archivos utilizados para firmar las aplicaciones en el momento de compilarlas. Los profiles de development te permiten compilar para ser instalados de forma directa en los dispositivos configurados (ver pasos anteriores), pero tienen una caducidad de 3 meses y no pueden tener más de 100 dispositivos dados de alta. Además, es necesario que el dispositvo tenga instalado el certificado para poder instalar la aplicación, pero como veremos si usamos las herramientas de desarrollo esto será automático. Si lo que pretendes es mandar la aplicación a una tercera persona, asegurate de que te ha dado su UDID, está dado de alta en el profile que corresponda y lo tiene instalado en su dispositivo antes de intentar instalar la aplicación. Para instalar el certificado en el dispositivo, al igual que una aplicación, puede hacerse también arrastrándolo a la biblioteca de su iTunes y sincronizando el dispositivo (cuidado porque en Windows no funciona bien, así que mejor si puede hacerlo en un Mac).<br />
<br />
<br />
<span style="font-size: 130%;"><span style="font-weight: bold;">Vistazo de las herramientas del SDK</span></span><br />
<br />
Tras la instalación del SDK tendrás 4 programas que utilizarás continuamente en tus desarrollos. Vamos a mencionarlos brevemente para que sepas para que se utilizan:<br />
<br />
<span style="font-weight: bold;">- XCode</span>: Este es el IDE de desarrollo, por lo que es la principal herramienta que utilizarás para programar. El XCode tiene muchas herramientas, atajos de teclados y truquitos, pero los irás aprendiendo con la práctica (posiblemente en el futuro haga un post sobre esto, pero ahora es demasiado). Lo principal de esta herramienta es que entiendas que a la izquierda tienes el árbol lógico de ficheros (digo lógico porque lo que organices en este árbol no se cambiará en el sistema de ficheros, sólo aquí dentro de XCode, así que cuidado con el desorden), y a la derecha la zona de escritura. Un elemento importante de esta herramienta es el selector que aparece arriba a la izquierda (entre el menú de la aplicación y el árbol de ficheros), con la que podrás elegir si quieres desplegar en un simulador o un dispositivo, así como la configuración (debug o release). Ambas configuraciones te sirven igual, únicamente es recomendable que uses el modo debug para hacer pruebas durante el desarrollo puesto que llevan más información de trazas. Volveremos a esto cuando hagamos un HelloWorld.<br />
<span style="font-weight: bold;">- Interface Builder</span>: Esta herramienta es el IDE gráfico. Te servirá para crear vistas de forma sencilla, arrastrando componentes desde una paleta de componentes sobre un lienzo, editar sus propiedades principales (color, tamaño, posición, opacidad, ...) y enazarlos con el código.<br />
<span style="font-weight: bold;">- iPhone Simulator</span>: Es un simulador de iPhone. Se arranca cuando configuras la aplicación para ejecutar sobre un simulador. A casi todos los efectos funciona igual que un dispositivo, pero es importante saber que este simulador no tiene límites de memoria y tiene una capacidad de CPU sensiblemente superior a los dispositivos reales. Por ello, no podemos estar seguros de que una aplicación funciona correctamente hasta probarla en un dispositivo auténtico. La gran ventaja es que se arranca e instalan aplicaciones más rápido que en un dispositivo real por lo que es muy útil para el día a día.<br />
<span style="font-weight: bold;">- Instruments</span>: Esta herramienta la usareis menos, pero nos permitirá revisar la aplicación en busca de leaks de memoria o consumo de CPU. No entraré en más detalle puesto que es un tema más avanzado.<br />
<br />
<span style="font-size: 130%;"><span style="font-weight: bold;">De aquí en adelante</span></span><br />
Y de momento esto es todo lo que considero importante para un post de introducción. No quiero extenderme más por ahora, pero el próximo día haré un pequeño HelloWorld donde explicaré en mayor profundidad las herramientas y en el futuro me meteré con temas más técnicos de Objective-C (el lenguaje de programación utilizado), arquitectura de Cocoa Touch (el framework sobre el que programamos), APIs, etc.</div>Angel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]1tag:blogger.com,1999:blog-4861031397020385591.post-76401721270931284442010-06-19T17:33:00.000+02:002012-05-19T22:10:20.182+02:00Servidores en Amazon con EC2 y EBS<div dir="ltr" style="text-align: left;" trbidi="on">
<b style="font-size: 21px;">Este post se ha movido de forma permanente a <a href="http://angelolloqui.com/blog/4-Servidores-en-Amazon-con-EC2-y-EBS">http://angelolloqui.com/blog/4-Servidores-en-Amazon-con-EC2-y-EBS</a></b><br />
<b style="font-size: 21px;"><br /></b><br />
Llevo ya más de un año pegándome con Amazon y su servicio AWS. Concretamente, con servidores virtuales EC2, almacenamiento en su sistema S3 y su interconexión con EBS.<br />
<br />
Hoy me esta tocando hacer una limpieza de una instancia que tengo con un antiguo cliente que resulta que se cayó recientemente y me ha apetecido compartir una reflexión y así escribir la primera entrada técnica (muy light), aunque no tiene nada que ver con dispositivos móviles.<br />
<br />
Así pues, antes de exponer el grave problema al que me podría haber enfrentado si no hubiese hecho las cosas bien, os expongo un poco las partes de AWS que estoy utilizando (hay más, pero estas son las más comunes):<br />
<br />
<a name='more'></a><br />
<br />
- <span style="font-weight: bold;">EC2</span> (Elastic Compute Cloud): Este es el servicio de Amazon que simula los servidores virtuales. Su funcionamiento es sencillo, seleccionas una imagen (AMI) de la que partir (puede ser una imagen genérica como una distribución Ubuntu o una imágen muy personalizada en la que tengas multitud de software ya configurado), seleccionas unas características de tu instancia en función del RAM, procesador, etc del que quieras disponer y la lanzas. En pocos segundos tendrás un servidor virtual con acceso vía SSH y con las características de tu AMI seleccionada. Sobre esta instancia, dado que es a todos los efectos un servidor virtual, puedes instalar el resto de software que desees, así como configurar cuentas, permisos,...<br />
<br />
- <span style="font-weight: bold;">S3</span> (Simple Storage Service): Este servicio proporciona un espacio persistente donde almacenar datos. A modo de simplificación, podemos entenderlo como un disco duro virtual y persistente.<br />
<br />
- <span style="font-weight: bold;">EBS</span> (Elastic Block Storage): Con este servicio podremos interconectar los dos anteriores, simulando que el S3 es una unidad más de nuestra instancia EC2. Quizá te estás preguntando la necesidad de este servicio. Pues bien, la respuesta es que EC2 por sí mismo no tiene persistencia de datos! sí sí, como lo has oido, las instancias EC2 son instancias virtuales, que tienen su sistema de ficheros como cualquier otro servidor, pero a diferencia de un servidor estándar, este sistema de ficheros es volátil, lo que significa que si por alguna razón tienes que terminar con la instancia perderás los datos que has introducido desde que lanzaste la instancia a partir de un AMI.<br />
<br />
- <span style="font-weight: bold;">Elastic IP</span>: Este servicio nos permite mapear cualquier instancia con una IP externa y única. Es necesario si queremos que este servidor esté accesible externamente por ejemplo por un nombre de dominio. La gran ventaja de esto es que al arrancar nuevas instancias podemos seguir usando la misma IP y así no tenemos que esperar a que los DNS se refresquen para cambiar de máquina (tardan hasta 48h).<br />
<br />
<br />
Y ahora que he comentado las principales partes implicadas, os expongo lo ocurrido: Una instancia que llevaba meses funcionando (y en producción), resulta que ha dejado de funcionar. No sólo el servidor web está caido, sino que la instancia está completamente inaccesible, sin posibilidad de conectarte por SSH, FTP ni similares! Arggghhhh!! <br />
¿Qué hacemos? pues no nos queda otra que entrar en la consola de administración de Amazon AWS, arrancar una nueva y apuntar la IP pública a la nueva, pero ¿perderemos todos los datos desde el último backup? esto sería muy grave porque además no hacemos backups con toda la frecuencia que deberíamos. Por otra parte, ¿que pasa con el software que instalé para adaptar mi instancia? ¿tendré que volver a instalarlo?<br />
<br />
Bueno, calma, para resolver el primer problema (y más grave) tenemos el EBS. Gracias a este servicio, toda nuestra BD estará trabajando en una unidad persistente, que NO habrá perdido la información y que podremos enlazar con la nueva instancia. Os dejo un enlace donde se explica una forma sencilla y clara sobre como montar este tipo de sistemas, moviendo los datos de MySQL a la unidad de EBS:<br />
<a href="http://lab.redmallorca.com/servidor-mysql-persistente-sobre-ec2-y-ebs/">http://lab.redmallorca.com/servidor-mysql-persistente-sobre-ec2-y-ebs/</a><br />
<br />
¿Y el segundo problema? ¿tengo que volver a instalar todo el software? pues la respuesta es que tendrías que hacerlo, a no ser que hayas sido precavido y hayas generado una nueva AMI a partir de tu instancia una vez configurada. ¿Como hacerlo?, de nuevo, os enlazo a un post en donde lo explica muy clarito:<br />
<a href="http://lab.redmallorca.com/crea-tu-propio-ami-basado-en-ubuntu-para-ec2/">http://lab.redmallorca.com/crea-tu-propio-ami-basado-en-ubuntu-para-ec2/</a><br />
<br />
<br />
<br />
Así que, para todos los que trabajéis con Amazon EC2, ni se os ocurra hacerlo sin contemplar posibles cortes o caídas porque ocurren! Mi consejo, aunque tengo poca experiencia, es que una vez instalado todo el software en tu instancia a partir de la AMI pública generes un AMI privada con esta configuración, posteriormente configures el sistema para que la BD trabaje sobre la unidad EBS, y por último, opcionalmente me crearía otra AMI privada con todo esto montado. Ojo, esta última AMI es posible que os de problemas al arrancar, por estar enlazada al EBS (a mí me los ha dado), por eso os recomiendo que hagáis la AMI preEBS para poder recuperar todo si tenéis problemas con esta segunda AMI (solo tendríais que repetir los pasos de montaje de EBS, pero los datos seguirían estando ahí y el software funcionando).<br />
<br />
<br />
Así que ya sabéis, haced las cosas bien al principio aunque os lleve un par de horas extras de trabajo y luego no tendréis que lamentar las pérdidas!</div>Angel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]0tag:blogger.com,1999:blog-4861031397020385591.post-45126062190205129572010-06-19T17:19:00.000+02:002012-05-19T22:11:00.049+02:00Mi primera entrada<div dir="ltr" style="text-align: left;" trbidi="on">
<b style="font-size: 21px;">Este blog<span id="goog_1972008505"></span><span id="goog_1972008506"></span><a href="http://www.blogger.com/"></a> se ha movido de forma permanente a <a href="http://angelolloqui.com/blog">http://angelolloqui.com/blog</a></b><br />
<b style="font-size: 21px;"><br /></b><br />
Si estás leyendo esto, bienvenido! esta es mi primera entrada en un blog que aún no sé que rumbo tomará.<br />
¿Por qué? porque simplemente aún no sé de que voy a hablar. Muchas veces me levanto pensando "esto debería compartirlo para que a nadie más le pase", "que dificil se hace trabajar con clientes como XXX", "que bueno lo nuevo en este SDK, versión, etc" o simplemente un "me siento bien!".<br />
<br />
Así que aquí estoy, escribiendo a la nada y a todos a la vez. De momento, lo único que sé seguro es que al principio habrá un poco de todo, comentarios técnicos muy centrados en desarrollo con plataformas móviles como iPhone y Android y alguna otra entrada que poco o nada tenga que ver.<br />
<br />
Saludos!</div>Angel García Olloquihttp://www.blogger.com/profile/05374594637619640066[email protected]0