<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Wilson Vargas]]></title><description><![CDATA[Un programador enamorado de su código.]]></description><link>https://www.wilsonvargas.com/</link><image><url>https://www.wilsonvargas.com/favicon.png</url><title>Wilson Vargas</title><link>https://www.wilsonvargas.com/</link></image><generator>Ghost 5.39</generator><lastBuildDate>Wed, 06 May 2026 00:33:44 GMT</lastBuildDate><atom:link href="https://www.wilsonvargas.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Cómo desactivar Liquid Glass para iOS 26 (y mantener tu UI estable)]]></title><description><![CDATA[Descubre cómo desactivar Liquid Glass en iOS 26 temporalmente y mantener la compatibilidad visual en tu app mientras adaptas tu diseño.]]></description><link>https://www.wilsonvargas.com/como-desactivar-liquid-glass-para-ios-26-y-mantener-tu-ui-estable/</link><guid isPermaLink="false">69974648de0c586e48ebbe08</guid><category><![CDATA[iOS]]></category><category><![CDATA[Maui]]></category><category><![CDATA[mobile]]></category><category><![CDATA[.NET9]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Fri, 20 Feb 2026 22:42:22 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2026/02/ChatGPT-Image-Feb-20--2026--05_04_47-PM-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2026/02/ChatGPT-Image-Feb-20--2026--05_04_47-PM-2.png" alt="C&#xF3;mo desactivar Liquid Glass para iOS&#x202F;26 (y mantener tu UI estable)"><p>La llegada de <strong>iOS&#x202F;26</strong> trajo consigo el nuevo lenguaje de dise&#xF1;o <em>Liquid Glass</em>, un aspecto visual con transparencias din&#xE1;micas y efecto &#x201C;vidrio l&#xED;quido&#x201D; que Apple implement&#xF3; a nivel de interfaz en toda la plataforma.</p><p>Si recientemente actualizaste tu entorno a <strong>macOS Tahoe</strong> y <strong>Xcode&#x202F;26</strong> y al compilar tu app en iOS&#x202F;26 encontraste que la IU se rompi&#xF3; o se ve inconsistente con tu sistema de dise&#xF1;o actual, este art&#xED;culo te explica <strong>c&#xF3;mo optar temporalmente por el dise&#xF1;o cl&#xE1;sico mientras adaptas tu UI al nuevo sistema</strong> &#x2014; una soluci&#xF3;n pr&#xE1;ctica que puedes implementar hoy mismo.</p><h2 id="%C2%BFqu%C3%A9-es-liquid-glass-y-por-qu%C3%A9-afecta-tu-app">&#xBF;Qu&#xE9; es Liquid Glass y por qu&#xE9; afecta tu app?</h2><p><em>Liquid Glass</em> es el nuevo lenguaje visual de Apple presentado oficialmente en junio de 2025. Reemplaza el dise&#xF1;o plano tradicional con una apariencia transl&#xFA;cida, reflejante y con profundidades tipo &#x201C;vidrio&#x201D;.</p><p>Aunque visualmente es atractivo, este cambio puede:</p><ul><li>Alterar colores de fondos y transparencias.</li><li>Afectar elementos del sistema como barras de navegaci&#xF3;n, tab bars, men&#xFA;s y modales.</li><li>Romper layouts que dependen de vistas s&#xF3;lidas o sin transparencia.</li></ul><p>Si tu dise&#xF1;o se basa en colores s&#xF3;lidos o un sistema visual con alta precisi&#xF3;n de contraste, la transici&#xF3;n puede provocar inconsistencia visual o comportamientos inesperados durante la ejecuci&#xF3;n en dispositivos con iOS&#x202F;26.</p><p>A m&#xED; me ha pasado con algunos controles usando MAUI, por ejemplo usando TitleView, algo parecido a esto:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2026/02/507266921-57a60770-1817-47b2-bbea-614ce5734d31.png" class="kg-image" alt="C&#xF3;mo desactivar Liquid Glass para iOS&#x202F;26 (y mantener tu UI estable)" loading="lazy" width="1640" height="2360" srcset="https://www.wilsonvargas.com/content/images/size/w600/2026/02/507266921-57a60770-1817-47b2-bbea-614ce5734d31.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2026/02/507266921-57a60770-1817-47b2-bbea-614ce5734d31.png 1000w, https://www.wilsonvargas.com/content/images/size/w1600/2026/02/507266921-57a60770-1817-47b2-bbea-614ce5734d31.png 1600w, https://www.wilsonvargas.com/content/images/2026/02/507266921-57a60770-1817-47b2-bbea-614ce5734d31.png 1640w" sizes="(min-width: 720px) 720px"></figure><h2 id="soluci%C3%B3n-desactivar-liquid-glass-temporalmente-en-tu-app">Soluci&#xF3;n: desactivar Liquid Glass temporalmente en tu app</h2><p>La buena noticia es que <strong>Apple provee un m&#xE9;todo oficial para &#x201C;optar por compatibilidad&#x201D; y desactivar provisoriamente el nuevo dise&#xF1;o &#x2003;visual en tu app mientras migras tu UI</strong>.</p><h3 id="1-editar-el-archivo-infoplist">1. Editar el archivo <code>Info.plist</code></h3><p>Abre el archivo <strong>Info.plist</strong> de tu proyecto y agrega la siguiente clave:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2026/02/1_y4v235pcJm4xWXMQrEbPAw.webp" class="kg-image" alt="C&#xF3;mo desactivar Liquid Glass para iOS&#x202F;26 (y mantener tu UI estable)" loading="lazy" width="867" height="215" srcset="https://www.wilsonvargas.com/content/images/size/w600/2026/02/1_y4v235pcJm4xWXMQrEbPAw.webp 600w, https://www.wilsonvargas.com/content/images/2026/02/1_y4v235pcJm4xWXMQrEbPAw.webp 867w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><pre><code>&lt;key&gt;UIDesignRequiresCompatibility&lt;/key&gt;
&lt;true/&gt;
</code></pre>
<!--kg-card-end: markdown--><ul><li>Esta configuraci&#xF3;n indica que tu app requiere compatibilidad con el dise&#xF1;o cl&#xE1;sico.</li><li>Al compilar con este flag, iOS&#x202F;26 renderizar&#xE1; tu app sin aplicar el estilo Liquid Glass, devolviendo un comportamiento visual similar al de versiones anteriores.</li></ul><h2 id="importante-solo-es-una-soluci%C3%B3n-temporal">Importante: solo es una soluci&#xF3;n temporal</h2><p>Apple ha dejado claro que esta compatibilidad <strong>estar&#xE1; disponible solo hasta iOS&#x202F;27</strong>, lo que significa que: </p><ul><li>A partir de iOS&#x202F;28 (y versiones futuras), el flag puede ser removido.</li><li>Lo ideal es que actualices tu UI para adoptar o adaptarse al nuevo lenguaje visual antes de que desaparezca esta opci&#xF3;n.</li></ul><h2 id="recomendaciones-para-migrar-tu-dise%C3%B1o">Recomendaciones para migrar tu dise&#xF1;o</h2><p>Mientras usas esta soluci&#xF3;n temporal, aqu&#xED; tienes algunas acciones recomendadas:</p><ul><li><strong>Audita los elementos de UI afectados:</strong><br>Revisa vistas que dependen de colores s&#xF3;lidos o layouts absolutamente posicionados.</li><li><strong>Eval&#xFA;a la legibilidad y contraste:</strong><br>El nuevo estilo puede afectar los niveles de legibilidad &#x2014; aseg&#xFA;rate de probar diferentes modos de contraste y transparencia en iOS&#x202F;26.</li><li><strong>Actualiza tus assets visuales:</strong><br>Iconos, fondos y componentes deben adaptarse para funcionar armoniosamente con transparencias y profundidad.</li></ul><h2 id="conclusi%C3%B3n">Conclusi&#xF3;n</h2><p>Si tu UI falla o se ve da&#xF1;ada al compilar y ejecutar tu app en iOS&#x202F;26 con <em>Liquid Glass</em>, puedes:</p><ul><li>A&#xF1;adir <code>UIDesignRequiresCompatibility = YES</code> en tu <strong>Info.plist</strong> para desactivar el nuevo estilo visual.</li><li> Ganar tiempo mientras actualizas tu dise&#xF1;o y pruebas adecuadamente en entornos reales.</li></ul><p>Esta t&#xE9;cnica no es solo &#xFA;til &#x2014; <strong>es estrat&#xE9;gica</strong> para mantener la experiencia de usuario intacta hasta que completes la migraci&#xF3;n a la nueva est&#xE9;tica de Apple.</p>]]></content:encoded></item><item><title><![CDATA[Cómo migrar SecureStorage de Xamarin.Essentials a .NET MAUI sin perder datos]]></title><description><![CDATA[Aprende cómo migrar SecureStorage de Xamarin a .NET MAUI paso a paso, sin perder datos ni pedir a tus usuarios que ingresen todo de nuevo.]]></description><link>https://www.wilsonvargas.com/como-migrar-securestorage-de-xamarin-essentials-a-net-maui-sin-perder-datos/</link><guid isPermaLink="false">68a8f1abd40f57821d0be653</guid><category><![CDATA[Maui]]></category><category><![CDATA[Multiplataforma]]></category><category><![CDATA[.NET9]]></category><category><![CDATA[Xamarin]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Android]]></category><category><![CDATA[mobile]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Fri, 22 Aug 2025 23:23:03 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2025/08/image-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2025/08/image-2.png" alt="C&#xF3;mo migrar SecureStorage de Xamarin.Essentials a .NET MAUI sin perder datos"><p>&#xBF;Tienes una app en Xamarin.Forms que ya guarda datos sensibles usando <code>SecureStorage</code>, y no quieres que tus usuarios reingresen todo al migrar a .NET MAUI? Aqu&#xED; te cuento paso a paso c&#xF3;mo hacerlo de forma segura y fluida.</p><!--kg-card-begin: markdown--><h2 id="por-qu%C3%A9-se-da%C3%B1a-la-migraci%C3%B3n-diferencias-clave-entre-plataformas">Por qu&#xE9; se da&#xF1;a la migraci&#xF3;n? diferencias clave entre plataformas</h2>
<!--kg-card-end: markdown--><p>Tanto Xamarin.Essentials como .NET MAUI tienen la clase <code>SecureStorage</code> para guardar pares de clave/valor de forma segura. Pero ojo: <strong>no funcionan igual a nivel interno en Android ni iOS</strong>.</p><!--kg-card-begin: html--><table>
<thead>
<tr>
<th>Plataforma</th>
<th>Xamarin.Essentials</th>
<th>.NET MAUI</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Android</strong></td>
<td>Se usa <strong>Android KeyStore</strong> para almacenar la clave de cifrado utilizada al encriptar un valor antes de guardarlo en un objeto <strong>SharedPreferences</strong> con el nombre <code inline>{tu-app-package-id}.xamarinessentials</code>.</td>
<td>Los datos se encriptan con la clase <strong>EncryptedSharedPreferences</strong>, que encapsula a <strong>SharedPreferences</strong> y cifra autom&#xE1;ticamente claves y valores. El nombre utilizado es <code inline>{tu-app-package-id}.microsoft.maui.essentials.preferences</code>.</td>
</tr>
<tr>
<td><strong>iOS</strong></td>
<td>Se usa <strong>KeyChain</strong> para almacenar valores de forma segura. El <strong>SecRecord</strong> utilizado tiene el campo <strong>Service</strong> configurado como <code inline>{tu-app-package-id}.xamarinessentials</code>.</td>
<td>Se usa <strong>KeyChain</strong> para almacenar valores de forma segura. El <strong>SecRecord</strong> utilizado tiene el campo <strong>Service</strong> configurado como <code inline>{tu-app-package-id}.microsoft.maui.essentials.preferences</code>.</td>
</tr>
</tbody>
</table><!--kg-card-end: html--><p>El resultado: si simplemente cambias el c&#xF3;digo, tu app .NET MAUI <strong>no encontrar&#xE1;</strong> lo que guardaste antes en Xamarin, porque est&#xE1; buscando en otro sitio.</p><!--kg-card-begin: markdown--><h2 id="soluci%C3%B3n-paso-a-paso-usar-legacysecurestorage">Soluci&#xF3;n paso a paso: usar LegacySecureStorage</h2>
<!--kg-card-end: markdown--><ul><li>Crea una clase <code>LegacySecureStorage</code> en tu proyecto .NET MAUI (puede ir en cualquier carpeta, por ejemplo dentro de <code>MigrationHelpers</code>).</li><li>Usa este c&#xF3;digo que te permite leer lo que ya estaba guardado con Xamarin.Essentials:</li></ul><!--kg-card-begin: markdown--><pre class="line-numbers language-csharp">
<code>#if ANDROID || IOS
using System;
using System.Threading.Tasks;
using Microsoft.Maui.ApplicationModel;
#if ANDROID
using Android.Content;
using Android.Security.Keystore;
using AndroidX.Security.Crypto;
using Java.Security;
using Javax.Crypto;
using Android.Preferences;
#endif
#if IOS
using Security;
using Foundation;
#endif

namespace TuApp.Helpers
{
    public static class LegacySecureStorage
    {
        internal static readonly string Alias = $&quot;{AppInfo.PackageName}.xamarinessentials&quot;;

#if ANDROID
        static readonly object locker = new object();

        public static Task<string?> GetAsync(string key)
        {
            lock (locker)
            {
                var context = Android.App.Application.Context;
                var sharedPrefs = context.GetSharedPreferences(Alias, FileCreationMode.Private);
                var encryptedValue = sharedPrefs.GetString(key, null);
                return Task.FromResult<string?>(encryptedValue);
            }
        }

        public static bool Remove(string key)
        {
            lock (locker)
            {
                var context = Android.App.Application.Context;
                var sharedPrefs = context.GetSharedPreferences(Alias, FileCreationMode.Private);
                var editor = sharedPrefs.Edit();
                editor.Remove(key);
                return editor.Commit();
            }
        }

        public static void RemoveAll()
        {
            lock (locker)
            {
                var context = Android.App.Application.Context;
                var sharedPrefs = context.GetSharedPreferences(Alias, FileCreationMode.Private);
                var editor = sharedPrefs.Edit();
                editor.Clear();
                editor.Commit();
            }
        }
#endif

#if IOS
        public static Task<string?> GetAsync(string key)
        {
            var query = new SecRecord(SecKind.GenericPassword)
            {
                Service = Alias,
                Account = key
            };

            var match = SecKeyChain.QueryAsRecord(query, out var resultCode);
            if (resultCode == SecStatusCode.Success &amp;&amp; match?.ValueData != null)
            {
                var value = NSString.FromData(match.ValueData, NSStringEncoding.UTF8);
                return Task.FromResult<string?>(value);
            }

            return Task.FromResult<string?>(null);
        }

        public static bool Remove(string key)
        {
            var query = new SecRecord(SecKind.GenericPassword)
            {
                Service = Alias,
                Account = key
            };

            var result = SecKeyChain.Remove(query);
            return result == SecStatusCode.Success;
        }

        public static void RemoveAll()
        {
            var query = new SecRecord(SecKind.GenericPassword)
            {
                Service = Alias
            };
            SecKeyChain.Remove(query);
        }
#endif
    }
}
#endif
</string?></string?></string?></string?></string?></code>
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="android">Android</h3>
<!--kg-card-end: markdown--><p>En Android, la clase <code>LegacySecureStorage</code> utiliza la clase <code>AndroidKeyStore</code> para almacenar la clave de cifrado que se usa al encriptar un valor antes de guardarlo en un objeto <code>SharedPreferences</code> con el nombre <code>{tu-app-package-id}.xamarinessentials</code>.</p><blockquote>Agrega la clase llamada <code>AndroidKeyStore</code> dentro de la carpeta <code>Platforms\Android</code> de tu proyecto .NET MAUI. </blockquote><!--kg-card-begin: markdown--><pre class="line-numbers language-csharp">
<code>using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Security;
using Android.Security.Keystore;
using Java.Security;
using Javax.Crypto;
using Javax.Crypto.Spec;
using System.Text;

namespace MigrationHelpers;

class AndroidKeyStore
{
    const string androidKeyStore = &quot;AndroidKeyStore&quot;; // this is an Android const value
    const string aesAlgorithm = &quot;AES&quot;;
    const string cipherTransformationAsymmetric = &quot;RSA/ECB/PKCS1Padding&quot;;
    const string cipherTransformationSymmetric = &quot;AES/GCM/NoPadding&quot;;
    const string prefsMasterKey = &quot;SecureStorageKey&quot;;
    const int initializationVectorLen = 12; // Android supports an IV of 12 for AES/GCM

    internal AndroidKeyStore(Context context, string keystoreAlias, bool alwaysUseAsymmetricKeyStorage)
    {
        alwaysUseAsymmetricKey = alwaysUseAsymmetricKeyStorage;
        appContext = context;
        alias = keystoreAlias;

        keyStore = KeyStore.GetInstance(androidKeyStore);
        keyStore.Load(null);
    }

    readonly Context appContext;
    readonly string alias;
    readonly bool alwaysUseAsymmetricKey;
    readonly string useSymmetricPreferenceKey = &quot;essentials_use_symmetric&quot;;

    KeyStore keyStore;
    bool useSymmetric = false;

    ISecretKey GetKey()
    {
        // check to see if we need to get our key from past-versions or newer versions.
        // we want to use symmetric if we are &gt;= 23 or we didn&apos;t set it previously.
        var hasApiLevel = Build.VERSION.SdkInt &gt;= BuildVersionCodes.M;

        useSymmetric = Preferences.Get(useSymmetricPreferenceKey, hasApiLevel, alias);

        // If &gt;= API 23 we can use the KeyStore&apos;s symmetric key
        if (useSymmetric &amp;&amp; !alwaysUseAsymmetricKey)
            return GetSymmetricKey();

        // NOTE: KeyStore in &lt; API 23 can only store asymmetric keys
        // specifically, only RSA/ECB/PKCS1Padding
        // So we will wrap our symmetric AES key we just generated
        // with this and save the encrypted/wrapped key out to
        // preferences for future use.
        // ECB should be fine in this case as the AES key should be
        // contained in one block.

        // Get the asymmetric key pair
        var keyPair = GetAsymmetricKeyPair();

        var existingKeyStr = Preferences.Get(prefsMasterKey, null, alias);

        if (!string.IsNullOrEmpty(existingKeyStr))
        {
            try
            {
                var wrappedKey = Convert.FromBase64String(existingKeyStr);

                var unwrappedKey = UnwrapKey(wrappedKey, keyPair.Private);
                var kp = unwrappedKey.JavaCast<isecretkey>();

                return kp;
            }
            catch (InvalidKeyException ikEx)
            {
                System.Diagnostics.Debug.WriteLine($&quot;Unable to unwrap key: Invalid Key. This may be caused by system backup or upgrades. All secure storage items will now be removed. {ikEx.Message}&quot;);
            }
            catch (IllegalBlockSizeException ibsEx)
            {
                System.Diagnostics.Debug.WriteLine($&quot;Unable to unwrap key: Illegal Block Size. This may be caused by system backup or upgrades. All secure storage items will now be removed. {ibsEx.Message}&quot;);
            }
            catch (BadPaddingException paddingEx)
            {
                System.Diagnostics.Debug.WriteLine($&quot;Unable to unwrap key: Bad Padding. This may be caused by system backup or upgrades. All secure storage items will now be removed. {paddingEx.Message}&quot;);
            }
            LegacySecureStorage.RemoveAll();
        }

        var keyGenerator = KeyGenerator.GetInstance(aesAlgorithm);
        var defSymmetricKey = keyGenerator.GenerateKey();

        var newWrappedKey = WrapKey(defSymmetricKey, keyPair.Public);

        Preferences.Set(prefsMasterKey, Convert.ToBase64String(newWrappedKey), alias);

        return defSymmetricKey;
    }

    // API 23+ Only
#pragma warning disable CA1416
    ISecretKey GetSymmetricKey()
    {
        Preferences.Set(useSymmetricPreferenceKey, true, alias);

        var existingKey = keyStore.GetKey(alias, null);

        if (existingKey != null)
        {
            var existingSecretKey = existingKey.JavaCast<isecretkey>();
            return existingSecretKey;
        }

        var keyGenerator = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, androidKeyStore);
        var builder = new KeyGenParameterSpec.Builder(alias, KeyStorePurpose.Encrypt | KeyStorePurpose.Decrypt)
            .SetBlockModes(KeyProperties.BlockModeGcm)
            .SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone)
            .SetRandomizedEncryptionRequired(false);

        keyGenerator.Init(builder.Build());

        return keyGenerator.GenerateKey();
    }
#pragma warning restore CA1416

    KeyPair GetAsymmetricKeyPair()
    {
        // set that we generated keys on pre-m device.
        Preferences.Set(useSymmetricPreferenceKey, false, alias);

        var asymmetricAlias = $&quot;{alias}.asymmetric&quot;;

        var privateKey = keyStore.GetKey(asymmetricAlias, null)?.JavaCast<iprivatekey>();
        var publicKey = keyStore.GetCertificate(asymmetricAlias)?.PublicKey;

        // Return the existing key if found
        if (privateKey != null &amp;&amp; publicKey != null)
            return new KeyPair(publicKey, privateKey);

        var originalLocale = Java.Util.Locale.Default;
        try
        {
            // Force to english for known bug in date parsing:
            // https://issuetracker.google.com/issues/37095309
            SetLocale(Java.Util.Locale.English);

            // Otherwise we create a new key
#pragma warning disable CA1416
            var generator = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, androidKeyStore);
#pragma warning restore CA1416

            var end = DateTime.UtcNow.AddYears(20);
            var startDate = new Java.Util.Date();
#pragma warning disable CS0618 // Type or member is obsolete
            var endDate = new Java.Util.Date(end.Year, end.Month, end.Day);
#pragma warning restore CS0618 // Type or member is obsolete

#pragma warning disable CS0618
            var builder = new KeyPairGeneratorSpec.Builder(Platform.AppContext)
                .SetAlias(asymmetricAlias)
                .SetSerialNumber(Java.Math.BigInteger.One)
                .SetSubject(new Javax.Security.Auth.X500.X500Principal($&quot;CN={asymmetricAlias} CA Certificate&quot;))
                .SetStartDate(startDate)
                .SetEndDate(endDate);

            generator.Initialize(builder.Build());
#pragma warning restore CS0618

            return generator.GenerateKeyPair();
        }
        finally
        {
            SetLocale(originalLocale);
        }
    }

    byte[] WrapKey(IKey keyToWrap, IKey withKey)
    {
        var cipher = Cipher.GetInstance(cipherTransformationAsymmetric);
        cipher.Init(CipherMode.WrapMode, withKey);
        return cipher.Wrap(keyToWrap);
    }

#pragma warning disable CA1416
    IKey UnwrapKey(byte[] wrappedData, IKey withKey)
    {
        var cipher = Cipher.GetInstance(cipherTransformationAsymmetric);
        cipher.Init(CipherMode.UnwrapMode, withKey);
        var unwrapped = cipher.Unwrap(wrappedData, KeyProperties.KeyAlgorithmAes, KeyType.SecretKey);
        return unwrapped;
    }
#pragma warning restore CA1416

    internal string Decrypt(byte[] data)
    {
        if (data.Length &lt; initializationVectorLen)
            return null;

        var key = GetKey();

        // IV will be the first 16 bytes of the encrypted data
        var iv = new byte[initializationVectorLen];
        Buffer.BlockCopy(data, 0, iv, 0, initializationVectorLen);

        Cipher cipher;

        // Attempt to use GCMParameterSpec by default
        try
        {
            cipher = Cipher.GetInstance(cipherTransformationSymmetric);
            cipher.Init(CipherMode.DecryptMode, key, new GCMParameterSpec(128, iv));
        }
        catch (InvalidAlgorithmParameterException)
        {
            // If we encounter this error, it&apos;s likely an old bouncycastle provider version
            // is being used which does not recognize GCMParameterSpec, but should work
            // with IvParameterSpec, however we only do this as a last effort since other
            // implementations will error if you use IvParameterSpec when GCMParameterSpec
            // is recognized and expected.
            cipher = Cipher.GetInstance(cipherTransformationSymmetric);
            cipher.Init(CipherMode.DecryptMode, key, new IvParameterSpec(iv));
        }

        // Decrypt starting after the first 16 bytes from the IV
        var decryptedData = cipher.DoFinal(data, initializationVectorLen, data.Length - initializationVectorLen);

        return Encoding.UTF8.GetString(decryptedData);
    }

    internal void SetLocale(Java.Util.Locale locale)
    {
        Java.Util.Locale.Default = locale;
        var resources = appContext.Resources;
        var config = resources.Configuration;

        if (Build.VERSION.SdkInt &gt;= BuildVersionCodes.N)
            config.SetLocale(locale);
        else
#pragma warning disable CS0618 // Type or member is obsolete
            config.Locale = locale;
#pragma warning restore CS0618 // Type or member is obsolete

#pragma warning disable CS0618 // Type or member is obsolete
        resources.UpdateConfiguration(config, resources.DisplayMetrics);
#pragma warning restore CS0618 // Type or member is obsolete
    }
}
</iprivatekey></isecretkey></isecretkey></code>
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="ios">iOS</h3>
<!--kg-card-end: markdown--><p>En iOS, la clase <code>LegacySecureStorage</code> utiliza la clase <code>KeyChain</code> para almacenar valores de forma segura. El <code>SecRecord</code> que se usa para guardar los valores tiene el campo <code>Service</code> configurado como <code>{tu-app-package-id}.xamarinessentials</code>. </p><blockquote>Agrega la clase llamada <code>KeyChain</code> dentro de la carpeta <code>Platforms\iOS</code> de tu proyecto .NET MAUI. </blockquote><!--kg-card-begin: markdown--><pre class="line-numbers language-csharp">
<code>using Foundation;
using Security;

namespace MigrationHelpers;

class KeyChain
{
    SecRecord ExistingRecordForKey(string key, string service)
    {
        return new SecRecord(SecKind.GenericPassword)
        {
            Account = key,
            Service = service
        };
    }

    internal string ValueForKey(string key, string service)
    {
        using (var record = ExistingRecordForKey(key, service))
        using (var match = SecKeyChain.QueryAsRecord(record, out var resultCode))
        {
            if (resultCode == SecStatusCode.Success)
                return NSString.FromData(match.ValueData, NSStringEncoding.UTF8);
            else
                return null;
        }
    }

    internal bool Remove(string key, string service)
    {
        using (var record = ExistingRecordForKey(key, service))
        using (var match = SecKeyChain.QueryAsRecord(record, out var resultCode))
        {
            if (resultCode == SecStatusCode.Success)
            {
                RemoveRecord(record);
                return true;
            }
        }
        return false;
    }

    internal void RemoveAll(string service)
    {
        using (var query = new SecRecord(SecKind.GenericPassword) { Service = service })
        {
            SecKeyChain.Remove(query);
        }
    }

    bool RemoveRecord(SecRecord record)
    {
        var result = SecKeyChain.Remove(record);
        if (result != SecStatusCode.Success &amp;&amp; result != SecStatusCode.ItemNotFound)
            throw new Exception($&quot;Error removing record: {result}&quot;);

        return true;
    }
}
</code>
</pre><!--kg-card-end: markdown--><p>Finalmente, en tu c&#xF3;digo, lee y migra datos de la siguiente manera:</p><!--kg-card-begin: markdown--><pre class="line-numbers language-csharp">
<code>// Ejemplo: migrar el &quot;username&quot;
string? username = await LegacySecureStorage.GetAsync(&quot;username&quot;);

if (!string.IsNullOrEmpty(username))
{
    // Guardar en MAUI SecureStorage
    await SecureStorage.Default.SetAsync(&quot;username&quot;, username);

    // Eliminar del almacenamiento antiguo
    LegacySecureStorage.Remove(&quot;username&quot;);
}
</code>
</pre><!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="en-resumen">En resumen</h2>
<!--kg-card-end: markdown--><p>MAUI y Xamarin.Essentials usan <strong>almacenamiento seguro con nombres/identificadores distintos a nivel interno</strong>.<br>Lo que guardaste con Xamarin <strong>no se encuentra</strong> autom&#xE1;ticamente al pasar a MAUI.</p><p><strong>Qu&#xE9; hacemos</strong><br>1. <strong>Leemos lo antiguo</strong> con <code>LegacySecureStorage</code> (apunta a los mismos contenedores/alias de Xamarin).<br>2. <strong>Lo guardamos de nuevo</strong> con <code>SecureStorage.Default</code> (el formato/ubicaci&#xF3;n que entiende MAUI).<br>3. <strong>Limpiamos lo viejo</strong> para no dejar basura y evitar inconsistencias.<br>4. <strong>Ejecutamos la migraci&#xF3;n al iniciar la app</strong> para que el usuario ni lo note.<br></p><p><strong>Recomendaci&#xF3;n extra</strong></p><ul><li>Si prefieres algo ya empaquetado, usa el plugin <strong><code>Plugin.Maui.FormsMigration</code></strong> para orquestar la migraci&#xF3;n.</li><li>Mant&#xE9;n <strong>el mismo Package Name (Android)</strong> y <strong>Bundle Identifier (iOS)</strong> para que los datos previos sean accesibles.</li><li>Haz una lista de <strong>claves cr&#xED;ticas</strong> (token, refresh_token, username, etc.) y verif&#xED;calas con logs en un build interno antes de publicar.</li></ul><p>Con esto, la migraci&#xF3;n queda <strong>transparente para el usuario</strong> (sin re-login ni reconfiguraciones) y tu base de datos segura queda alineada al modelo de MAUI. Nos leemos pronto! </p>]]></content:encoded></item><item><title><![CDATA[Cómo agregar código específico de cada plataforma a un proyecto .NET MAUI]]></title><description><![CDATA[Aprende a agregar código específico por plataforma en .NET MAUI usando directivas y buenas prácticas para apps multiplataforma efectivas.]]></description><link>https://www.wilsonvargas.com/como-agregar-codigo-especifico-de-una-plataforma-a-un-proyecto-net-maui/</link><guid isPermaLink="false">685f4ac15101d431a558f266</guid><category><![CDATA[Maui]]></category><category><![CDATA[.NET9]]></category><category><![CDATA[Multiplataforma]]></category><category><![CDATA[Android]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Sat, 28 Jun 2025 18:29:41 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2025/06/imagenfinal-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2025/06/imagenfinal-2.png" alt="C&#xF3;mo agregar c&#xF3;digo espec&#xED;fico de cada plataforma a un proyecto .NET MAUI"><p>Est&#xE1;s desarrollando con .NET MAUI y todo va bien&#x2026; hasta que necesitas acceder a algo espec&#xED;fico de Android o iOS. D&#xF3;nde va ese c&#xF3;digo? C&#xF3;mo lo haces sin romper la portabilidad? Y, sobre todo, por qu&#xE9; no hay una gu&#xED;a clara que lo explique?</p><p>MAUI tiene una base de c&#xF3;digo &#xFA;nica, pero hay momentos en los que s&#xED; o s&#xED; necesitas tocar el sistema operativo.</p><p>En este art&#xED;culo vas a aprender c&#xF3;mo agregar c&#xF3;digo espec&#xED;fico de cada plataforma de una forma ordenada, usando las herramientas que MAUI te da, por ejemplo: directivas de compilaci&#xF3;n, clases parciales o inyecci&#xF3;n de dependencias. Todo explicado de forma directa y con ejemplos.</p><!--kg-card-begin: markdown--><h2 id="%C2%BFqu%C3%A9-significa-%E2%80%9Cc%C3%B3digo-espec%C3%ADfico-de-cada-plataforma%E2%80%9D-en-maui">&#xBF;Qu&#xE9; significa &#x201C;c&#xF3;digo espec&#xED;fico de cada plataforma&#x201D; en MAUI?</h2>
<!--kg-card-end: markdown--><p>Cuando hablamos de &#x201C;c&#xF3;digo espec&#xED;fico de cada plataforma&#x201D; en .NET MAUI, nos referimos a cualquier funcionalidad que no est&#xE1; disponible de forma compartida entre plataformas. Es decir, cosas que solo existen o funcionan diferente en Android, iOS, Windows o macOS.</p><p>Ejemplos comunes:</p><ul><li>Obtener el identificador del dispositivo (Android/iOS).</li><li>Acceder a sensores o al GPS.</li><li>Usar APIs exclusivas como el llavero de iOS o el sistema de archivos de Windows.</li></ul><p>MAUI, por dise&#xF1;o, te da una &#xFA;nica base de c&#xF3;digo. Pero tambi&#xE9;n sabe que vas a necesitar romper esa uniformidad en ciertos casos. Por eso, permite introducir c&#xF3;digo espec&#xED;fico sin tener que duplicar tu aplicaci&#xF3;n por cada plataforma.</p><p>El objetivo no es evitar el c&#xF3;digo nativo, sino encapsularlo bien. As&#xED; puedes seguir escribiendo tu app como si todo fuera multiplataforma&#x2026; y solo abrir esa &#x201C;puerta&#x201D; nativa cuando lo necesites.</p><!--kg-card-begin: markdown--><h2 id="estructura-del-proyecto-net-maui-y-d%C3%B3nde-va-el-c%C3%B3digo-nativo">Estructura del proyecto .NET MAUI y d&#xF3;nde va el c&#xF3;digo nativo</h2>
<!--kg-card-end: markdown--><p>En un proyecto .NET MAUI, todo empieza en una carpeta &#xFA;nica y compartida. Pero si miras un poco m&#xE1;s de cerca, vas a ver subcarpetas para cada plataforma: <code>Platforms/Android</code>, <code>Platforms/iOS</code>, <code>Platforms/Windows</code>, y <code>Platforms/MacCatalyst</code>. Ah&#xED; es donde va el c&#xF3;digo espec&#xED;fico.</p><p>Por ejemplo, si necesitas usar una API nativa de Android, ese c&#xF3;digo va dentro de <code>Platforms/Android</code>. Lo mismo con iOS o cualquier otra plataforma.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2025/06/1.png" class="kg-image" alt="C&#xF3;mo agregar c&#xF3;digo espec&#xED;fico de cada plataforma a un proyecto .NET MAUI" loading="lazy" width="173" height="249"></figure><p>Lo importante es que este c&#xF3;digo <strong>no se ejecuta autom&#xE1;ticamente</strong>. MAUI no mezcla todo; solo compila el c&#xF3;digo de la plataforma que est&#xE1;s ejecutando. Por eso es seguro tener c&#xF3;digo espec&#xED;fico ah&#xED;: no afecta a las otras plataformas.</p><p>Esto mantiene el c&#xF3;digo compartido limpio, y solo compila lo que corresponde seg&#xFA;n la plataforma.</p><!--kg-card-begin: markdown--><h2 id="uso-de-directivas-de-compilaci%C3%B3n-if-android-if-ios-etc">Uso de directivas de compilaci&#xF3;n (#if ANDROID, #if IOS, etc.)</h2>
<!--kg-card-end: markdown--><p>Las directivas de compilaci&#xF3;n son la forma m&#xE1;s directa de escribir c&#xF3;digo que solo se ejecute en una plataforma espec&#xED;fica. B&#xE1;sicamente, le dices al compilador: &#x201C;esto solo comp&#xED;lalo si estoy en Android&#x201D; (o iOS, o Windows).</p><!--kg-card-begin: html--><pre class="line-numbers language-csharp">
<code>public class IdentifierInfoService
{
    public string GetIndentifier()
    {
#if ANDROID
        return Android.Provider.Settings.Secure.GetString(
            Android.App.Application.Context.ContentResolver,
            Android.Provider.Settings.Secure.AndroidId);
#elif IOS
        return UIKit.UIDevice.CurrentDevice.IdentifierForVendor.AsString();
#else
        return &quot;No soportado en esta plataforma&quot;;
#endif
    }
}</code>
</pre><!--kg-card-end: html--><!--kg-card-begin: markdown--><h3 id="uso-en-tu-app">Uso en tu app:</h3>
<!--kg-card-end: markdown--><p>Puedes utilizar esta clase directamente en cualquier parte del c&#xF3;digo compartido. Por ejemplo, en tu <code>MainPage.xaml.cs</code>:</p><!--kg-card-begin: html--><pre class="line-numbers language-csharp">
<code>public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        var service = new IdentifierInfoService();
        string id = service.GetIndentifier();

        result.Text = $&quot;ID del dispositivo: {id}&quot;;
    }
}</code>
</pre><!--kg-card-end: html--><p>Esto funciona bien si el c&#xF3;digo espec&#xED;fico es simple y no lo usas en muchos lugares. Pero si empieza a crecer, puede volverse dif&#xED;cil de mantener. En ese caso, es mejor encapsularlo usando interfaces y separar las implementaciones por plataforma (lo veremos m&#xE1;s adelante).</p><p><strong>&#x2705; Cu&#xE1;ndo usarlo:</strong></p><ul><li>Cuando solo necesitas una peque&#xF1;a diferencia entre plataformas.</li><li>Para pruebas r&#xE1;pidas o funcionalidades m&#xED;nimas.</li></ul><p><strong>&#x274C; Cu&#xE1;ndo evitarlo:</strong></p><ul><li>Cuando el c&#xF3;digo nativo empieza a repetirse o a crecer.</li><li>Si quieres mantener el proyecto limpio y escalable.</li></ul><!--kg-card-begin: markdown--><h2 id="uso-de-partial-clases-y-m%C3%A9todos-parciales">Uso de partial clases y m&#xE9;todos parciales</h2>
<!--kg-card-end: markdown--><p>Otra forma elegante de manejar c&#xF3;digo espec&#xED;fico de plataforma en .NET MAUI es con <code>partial class</code> y <code>partial methods</code>.</p><p>Esto te permite definir la estructura de tu clase o m&#xE9;todo en el c&#xF3;digo compartido, pero escribir su implementaci&#xF3;n espec&#xED;fica en los archivos de cada plataforma.</p><p>Por ejemplo, en tu c&#xF3;digo compartido:</p><!--kg-card-begin: html--><pre class="line-numbers language-csharp">
<code>namespace Sample.Services;
public partial class DeviceInfoService
{
    public string GetIdentifier() =&gt; GetIdentifierInternal();

    protected static partial string GetIdentifierInternal();
}</code>
</pre><!--kg-card-end: html--><blockquote>Te presente el nombre de espacio que le pongas a esta clase, ya que en las siguientes clases que vas a crear tiene que ser el mismo.</blockquote><p>Ahora, en la carpeta <code>Platforms/Android</code> crea la clase que acceder&#xE1; al c&#xF3;digo nativo de Android:</p><!--kg-card-begin: markdown--><pre class="line-numbers language-csharp">
<code>namespace Sample.Services;
public partial class IdentifierInfoService
{
    protected static partial string GetIdentifierInternal()
    {
        return Android.Provider.Settings.Secure.GetString(
               Android.App.Application.Context.ContentResolver,
               Android.Provider.Settings.Secure.AndroidId);
    }
}</code>
</pre><!--kg-card-end: markdown--><p>Y finalmente lo mismo en la carpeta <code>Platforms/iOS</code>para acceder al c&#xF3;digo nativo de iOS:</p><!--kg-card-begin: html--><pre class="line-numbers language-csharp">
<code>namespace Sample.Services;
public partial class IdentifierInfoService
{
    protected static partial string GetIdentifierInternal()
    {
        return UIKit.UIDevice.CurrentDevice.IdentifierForVendor.AsString();
    }
}</code>
</pre><!--kg-card-end: html--><!--kg-card-begin: markdown--><h3 id="uso-en-tu-app">Uso en tu app:</h3>
<!--kg-card-end: markdown--><p>Puedes utilizar esta clase directamente en cualquier parte del c&#xF3;digo compartido. Por ejemplo, en tu <code>MainPage.xaml.cs</code>:</p><!--kg-card-begin: html--><pre class="line-numbers language-csharp">
<code>public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        var service = new IdentifierInfoService();
        string id = service.GetIndentifier();

        result.Text = $&quot;ID del dispositivo: {id}&quot;;
    }
}</code>
</pre><!--kg-card-end: html--><p>Con esto, todo tu c&#xF3;digo compartido sigue limpio y no necesitas preocuparse por las plataformas. Solo hace una llamada, y MAUI se encarga del resto en tiempo de compilaci&#xF3;n.</p><p><strong>Ventajas:</strong></p><ul><li>C&#xF3;digo m&#xE1;s limpio y organizado.</li><li>F&#xE1;cil de mantener y escalar.</li><li>Separaci&#xF3;n clara entre l&#xF3;gica compartida y l&#xF3;gica nativa.</li></ul><!--kg-card-begin: markdown--><h2 id="inyecci%C3%B3n-de-dependencias-con-interfaces-espec%C3%ADficas-por-plataforma">Inyecci&#xF3;n de dependencias con interfaces espec&#xED;ficas por plataforma</h2>
<!--kg-card-end: markdown--><p>Cuando tu l&#xF3;gica espec&#xED;fica de cada plataforma crece o necesitas una soluci&#xF3;n m&#xE1;s escalable, usar interfaces con inyecci&#xF3;n de dependencias es la forma m&#xE1;s limpia y flexible de manejarlo en MAUI.</p><!--kg-card-begin: markdown--><h3 id="1-crear-una-interfaz-en-tu-proyecto-compartido">1: Crear una interfaz en tu proyecto compartido</h3>
<!--kg-card-end: markdown--><!--kg-card-begin: html--><pre class="line-numbers language-csharp">
<code>public interface IIdentifierInfoService
{
    public string GetIdentifier();
}</code>
</pre><!--kg-card-end: html--><!--kg-card-begin: markdown--><h3 id="2-implementaci%C3%B3n-en-android">2: Implementaci&#xF3;n en Android</h3>
<!--kg-card-end: markdown--><p>En la carpeta <code>Platforms/Android</code> crea una clase llamada <code>IdentifierInfoService.cs</code>que implemente a la interface creada anteriormente:</p><!--kg-card-begin: html--><pre class="line-numbers language-csharp">
<code>public class IdentifierInfoService : IIdentifierInfoService
{
    public string GetIdentifier()
    {
        return Android.Provider.Settings.Secure.GetString(
               Android.App.Application.Context.ContentResolver,
               Android.Provider.Settings.Secure.AndroidId);
    }
}</code>
</pre><!--kg-card-end: html--><!--kg-card-begin: markdown--><h3 id="2-implementaci%C3%B3n-en-ios">2: Implementaci&#xF3;n en iOS</h3>
<!--kg-card-end: markdown--><p>En la carpeta <code>Platforms/iOS</code> crea una clase llamada <code>IdentifierInfoService.cs</code>que implemente a la interface creada anteriormente:</p><!--kg-card-begin: html--><pre class="line-numbers language-csharp">
<code>public class IdentifierInfoService : IIdentifierInfoService
{
    public string GetIdentifier()
    {
        return UIKit.UIDevice.CurrentDevice.IdentifierForVendor.AsString();
    }
}</code>
</pre><!--kg-card-end: html--><!--kg-card-begin: markdown--><h3 id="3-registro-en-mauiprogramcs">3. Registro en MauiProgram.cs</h3>
<!--kg-card-end: markdown--><p>En la clase <code>MauiProgram.cs</code> agrega el registro de cada implementaci&#xF3;n:</p><!--kg-card-begin: markdown--><pre><code>#if ANDROID
    builder.Services.AddSingleton&lt;IDeviceInfoService, DeviceInfoService&gt;();
#elif IOS
    builder.Services.AddSingleton&lt;IDeviceInfoService, DeviceInfoService&gt;();
#endif
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="4-usarlo-en-cualquier-parte-de-tu-app">4: Usarlo en cualquier parte de tu app</h3>
<!--kg-card-end: markdown--><p>En una p&#xE1;gina o ViewModel, inyectas la dependencia:</p><!--kg-card-begin: html--><pre class="line-numbers language-csharp">
<code>public partial class MainPage : ContentPage
{
    private readonly IIdentifierInfoService _deviceInfoService;

    public MainPage(IIdentifierInfoService deviceInfoService)
    {
        InitializeComponent();
        _deviceInfoService = deviceInfoService;

        string id = _deviceInfoService.GetIdentifier();
        result.Text = $&quot;ID del dispositivo: {id}&quot;;
    }
}</code>
</pre><!--kg-card-end: html--><p>Y listo. Con esto est&#xE1;s obteniendo un identificador nativo, espec&#xED;fico de cada plataforma, pero sin ensuciar tu c&#xF3;digo compartido.</p><!--kg-card-begin: markdown--><h2 id="buenas-pr%C3%A1cticas-para-mantener-tu-proyecto-limpio-y-escalable">Buenas pr&#xE1;cticas para mantener tu proyecto limpio y escalable</h2>
<!--kg-card-end: markdown--><p>Cuando trabajas con c&#xF3;digo espec&#xED;fico de cada plataforma en MAUI, es f&#xE1;cil caer en un caos de archivos y l&#xF3;gica duplicada. Estas pr&#xE1;cticas te van a ayudar a evitarlo:</p><h3 id="1-usa-interfaces-siempre-que-puedas">1. <strong>Usa interfaces siempre que puedas</strong></h3><p>Separar la definici&#xF3;n (interface) de la implementaci&#xF3;n te da flexibilidad, testabilidad y un c&#xF3;digo m&#xE1;s limpio.</p><h3 id="2-evita-abusar-de-las-directivas-de-compilaci%C3%B3n">2. <strong>Evita abusar de las directivas de compilaci&#xF3;n</strong></h3><p>Si bien <code>#if ANDROID</code>, <code>#if IOS</code> son &#xFA;tiles, pueden volver el c&#xF3;digo dif&#xED;cil de leer si se usan en exceso. Si necesitas l&#xF3;gica m&#xE1;s compleja, encapsula todo en clases separadas.</p><h3 id="3-organiza-tus-archivos-por-plataforma">3. <strong>Organiza tus archivos por plataforma</strong></h3><p>No pongas c&#xF3;digo espec&#xED;fico en archivos gen&#xE9;ricos. Aprovecha la estructura de carpetas (<code>Platforms/Android</code>, etc.) para mantener todo ordenado.</p><h3 id="4-centraliza-el-registro-de-dependencias">4. <strong>Centraliza el registro de dependencias</strong></h3><p>Mant&#xE9;n el registro de servicios (en <code>MauiProgram.cs</code>) lo m&#xE1;s claro posible. Agrupa los <code>AddSingleton</code> por plataforma y comenta lo necesario.</p><h3 id="6-piensa-en-el-futuro">6. <strong>Piensa en el futuro</strong></h3><p>Si est&#xE1;s empezando con una implementaci&#xF3;n simple, no te preocupes. Pero si sabes que vas a expandir esa l&#xF3;gica (por ejemplo, m&#xE1;s APIs nativas), es mejor arrancar con interfaces desde el principio.</p><!--kg-card-begin: markdown--><h2 id="conclusi%C3%B3n">Conclusi&#xF3;n</h2>
<!--kg-card-end: markdown--><p>Agregar c&#xF3;digo espec&#xED;fico de cada plataforma en .NET MAUI no es una trampa al sistema ni un retroceso: es una necesidad real en muchas apps, y MAUI est&#xE1; dise&#xF1;ado para manejarlo bien&#x2026; siempre que t&#xFA; lo manejes bien tambi&#xE9;n.</p><p>Ya viste:</p><ul><li>D&#xF3;nde va el c&#xF3;digo espec&#xED;fico en el proyecto.</li><li>C&#xF3;mo usar directivas de compilaci&#xF3;n para diferencias simples.</li><li>C&#xF3;mo usar <code>partial class</code> e <code>interface</code> para mantener todo limpio.</li><li>Buenas pr&#xE1;cticas y errores que pod&#xE9;s evitar desde el primer d&#xED;a.</li></ul><p><strong>&#xBF;Qu&#xE9; sigue ahora?</strong></p><ul><li>Revisa qu&#xE9; partes de tu app podr&#xED;an beneficiarse de l&#xF3;gica espec&#xED;fica por plataforma.</li><li>Usa el patr&#xF3;n que m&#xE1;s sentido tenga: directo, parcial o con interfaces.</li><li>Prueba en cada plataforma donde tu app vaya a vivir.</li></ul><p>Y recuerda: escribir c&#xF3;digo nativo no es romper el concepto de MAUI. Es entenderlo bien. Nos leemos pronto :)</p>]]></content:encoded></item><item><title><![CDATA[Cómo instalar Docker en Windows 11 (WSL) sin Docker Desktop]]></title><description><![CDATA[En este tutorial, aprenderás cómo instalar Docker en Windows 11 utilizando WSL2 sin necesidad de utilizar Docker Desktop.]]></description><link>https://www.wilsonvargas.com/como-instalar-docker-en-windows-11/</link><guid isPermaLink="false">6416018a25be7bc132b5eb7a</guid><category><![CDATA[Docker]]></category><category><![CDATA[Windows]]></category><category><![CDATA[WSL]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Mon, 01 May 2023 22:46:00 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2023/05/977565_538256596215839_735365046_o_1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2023/05/977565_538256596215839_735365046_o_1.jpg" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop"><p>Docker es una herramienta de contenedores que permite a los desarrolladores crear, ejecutar y distribuir aplicaciones en diferentes entornos. A su vez Windows Subsystem for Linux 2 (WSL 2) cuenta con un kernel de Linux real, compatible con contenedores Docker.</p><p>Docker funciona en WSL 2 y Docker Desktop es la forma m&#xE1;s f&#xE1;cil de instalar Docker en Windows, sin embargo, para algunas personas (y me incluyo) suele ser un poco tedioso y pesado. Sobre todo, estar esperando que la ballenita se quede cargando por un largo rato en tu barra de tareas o simplemente no te gusta tener muchos programas abierto al momento de desarrollar. En esas ocasiones entonces quiz&#xE1;s quieras ejecutar Docker desde WSL 2 sin necesidad de usar Docker Desktop.</p><p>Adem&#xE1;s, ten en cuenta que Docker Desktop es gratuito solo para personas o peque&#xF1;as empresas. Si lo est&#xE1;s utilizando en el trabajo y tu empresa supera una cierta cantidad de empleados o ingresos, entonces deber&#xED;as pagar una suscripci&#xF3;n. Para ver m&#xE1;s detalles sobre el modelo de suscripci&#xF3;n de Docker puedes ir <a href="https://www.docker.com/blog/the-grace-period-for-the-docker-subscription-service-agreement-ends-soon-heres-what-you-need-to-know/?ref=wilsonvargas.com">aqu&#xED;</a>.</p><h2 id="habilita-wsl-2-en-windows-11">Habilita WSL 2 en Windows 11</h2><p>Lo primero que debes hacer es habilitar la caracter&#xED;stica de WSL en Windows 11. Para hacerlo, presionar Windows y escribir &quot;Agregar caracter&#xED;sticas de Windows&quot; </p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/03/1.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="775" height="726" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/03/1.png 600w, https://www.wilsonvargas.com/content/images/2023/03/1.png 775w" sizes="(min-width: 720px) 720px"></figure><p>Luego en la ventana que se abrir&#xE1;, buscar la opci&#xF3;n &quot;Windows Subsystem for Linux&quot;, activa la casilla y reinicia.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/03/2.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="480" height="401"></figure><h2 id="descarga-e-instala-una-distribuci%C3%B3n-de-linux">Descarga e instala una distribuci&#xF3;n de Linux</h2><p>Una vez que hayas habilitado WSL en Windows 11, necesitar&#xE1;s descargar e instalar una distro de Linux para ejecutar Docker. Puedes elegir cualquier distro de Linux que desees, pero en este ejemplo usaremos <strong>Ubuntu</strong>. Para descargar Ubuntu, ve a la tienda Microsoft Store y busca &quot;Ubuntu&quot;:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/03/4-1.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1252" height="829" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/03/4-1.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/03/4-1.png 1000w, https://www.wilsonvargas.com/content/images/2023/03/4-1.png 1252w" sizes="(min-width: 720px) 720px"></figure><h2 id="prepar%C3%A1ndose-para-la-instalaci%C3%B3n-de-docker">Prepar&#xE1;ndose para la instalaci&#xF3;n de Docker</h2><p>Primero vamos a hacer <strong>update </strong>y <strong>upgrade </strong>de los paquetes de nuestra distro:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>sudo apt update &amp;&amp; sudo apt upgrade</code>
</pre><!--kg-card-end: html--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/03/5.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1119" height="632" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/03/5.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/03/5.png 1000w, https://www.wilsonvargas.com/content/images/2023/03/5.png 1119w" sizes="(min-width: 720px) 720px"></figure><p>Luego instalamos algunas dependencias:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>sudo apt install --no-install-recommends apt-transport-https ca-certificates curl gnupg2</code>
</pre><!--kg-card-end: html--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/03/6.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1119" height="632" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/03/6.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/03/6.png 1000w, https://www.wilsonvargas.com/content/images/2023/03/6.png 1119w" sizes="(min-width: 720px) 720px"></figure><p>A continuaci&#xF3;n, configuramos las direcciones en el repositorio de paquetes, para eso primero vamos configurar temporalmente algunas variables del sistema operativo:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>. /etc/os-release</code>
</pre><!--kg-card-end: html--><!--kg-card-begin: markdown--><p>Luego nos aseguramos de que la ruta de descarga sea una ruta segura para <code>apt</code>:</p>
<!--kg-card-end: markdown--><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>curl -fsSL https://download.docker.com/linux/${ID}/gpg | sudo tee /etc/apt/trusted.gpg.d/docker.asc</code>
</pre><!--kg-card-end: html--><p>Ahora agregamos y actualizamos el repositorio para que est&#xE9; listo:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>echo &quot;deb [arch=amd64] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable&quot; | sudo tee /etc/apt/sources.list.d/docker.list</code>
</pre><!--kg-card-end: html--><!--kg-card-begin: markdown--><p>Hacemos un &#xFA;ltimo <code>sudo apt update</code> y con esto estamos listos para instalar Docker.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><blockquote>
<p>Si eres observador habr&#xE1;s notado que he usado <code>${ID}</code> y <code>${VERSION_CODENAME}</code>. Bueno, estas son variables del sistema, las puedes ubicar si usas el siguiente comando: <code>nano /etc/os-release</code></p>
</blockquote>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/05/7.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1116" height="627" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/05/7.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/05/7.png 1000w, https://www.wilsonvargas.com/content/images/2023/05/7.png 1116w" sizes="(min-width: 720px) 720px"></figure><h2 id="instalando-docker">Instalando Docker</h2><p>Ahora podemos instalar el engine de docker y algunas herramientas adicionales, para eso ejecutamos el siguiente comando:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin</code>
</pre><!--kg-card-end: html--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/05/8-1.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1116" height="628" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/05/8-1.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/05/8-1.png 1000w, https://www.wilsonvargas.com/content/images/2023/05/8-1.png 1116w" sizes="(min-width: 720px) 720px"></figure><h2 id="administra-docker-sin-usar-sudo">Administra Docker sin usar sudo</h2><p>El daemon Docker se vincula a un socket de Unix, no a un puerto TCP. Por defecto, es el usuario root es el que el puede acceder al socket de Unix, otros usuarios solo pueden acceder a &#xE9;l mediante sudo.</p><p>Entonces tienes 2 caminos, acceder a este socket usando sudo todo el tiempo o creando un grupo y d&#xE1;ndole permisos a usuarios espec&#xED;ficos. En esta ocasi&#xF3;n iremos por la segunda opci&#xF3;n:</p><!--kg-card-begin: markdown--><p>Creamos un grupo llamado <code>docker</code></p>
<!--kg-card-end: markdown--><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>sudo groupadd docker</code>
</pre><!--kg-card-end: html--><!--kg-card-begin: markdown--><p>Ahora agregamos nuestro usuario al grupo <code>docker</code></p>
<!--kg-card-end: markdown--><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>sudo usermod -aG docker $USER</code>
</pre><!--kg-card-end: html--><!--kg-card-begin: markdown--><p>Cierra la ventana de terminar y vu&#xE9;lvela. Deber&#xED;as de ver el grupo <code>docker</code> cuando escribas el comando <code>groups</code> de la siguiente manera:</p>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/05/9.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1121" height="636" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/05/9.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/05/9.png 1000w, https://www.wilsonvargas.com/content/images/2023/05/9.png 1121w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><h2 id="lanzando-dockerd">Lanzando <code>dockerd</code></h2>
<!--kg-card-end: markdown--><p>Casi todas las distros Linux usan <code>systemd</code> u otro sistema de inicio (init), pero WSL tiene su propio sistema de inicio. Para no complicarnos la vida vamos a usar el sistema de inicio de WSL, solo lanzamos <code>dockerd</code> y vemos la magia:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>sudo dockerd</code>
</pre><!--kg-card-end: html--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/05/10.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1117" height="629" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/05/10.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/05/10.png 1000w, https://www.wilsonvargas.com/content/images/2023/05/10.png 1117w" sizes="(min-width: 720px) 720px"></figure><p>Abre otra terminal y prueba el docker cli:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>docker run --rm hello-world</code>
</pre><!--kg-card-end: html--><p>Deber&#xED;as ver un mensaje de &quot;Hello from Docker!&quot; en la terminal:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/05/11.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1110" height="633" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/05/11.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/05/11.png 1000w, https://www.wilsonvargas.com/content/images/2023/05/11.png 1110w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><h2 id="iniciando-dockerd-autom%C3%A1ticamente">Iniciando <code>dockerd</code> autom&#xE1;ticamente</h2>
<!--kg-card-end: markdown--><p>Lo que haremos a continuaci&#xF3;n es hacer que <code>dockerd</code>se ejecute autom&#xE1;ticamente al iniciar la terminal, para esto tendremos que editar el archivo <code>~./profile</code>.</p><p>Primero abrimos el archivo <code>~./profile</code>usando <code>nano</code>:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>sudo nano ~./profile</code>
</pre><!--kg-card-end: html--><p>Y a continuaci&#xF3;n agregamos las siguientes sentencias al final del archivo:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>DOCKER_DISTRO=&quot;Ubuntu-20.04&quot;
DOCKER_LOG_DIR=$HOME/docker_logs
mkdir -pm o=,ug=rwx &quot;$DOCKER_LOG_DIR&quot;
/mnt/c/Windows/System32/wsl.exe -d $DOCKER_DISTRO sh -c &quot;nohup sudo -b dockerd &lt; /dev/null &gt; $DOCKER_LOG_DIR/dockerd.log 2&gt;&amp;1&quot;</code>
</pre><!--kg-card-end: html--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/05/12.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1116" height="631" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/05/12.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/05/12.png 1000w, https://www.wilsonvargas.com/content/images/2023/05/12.png 1116w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: markdown--><blockquote>
<p>Ten en cuenta que a la variable <code>DOCKER_DISTRO</code> se le debe asignar el nombre correcto de la distribuci&#xF3;n que est&#xE9;s usando. Si no est&#xE1;s seguro del nombre exacto de tu distro, ejecuta <code>wsl -l -q</code> desde tu terminal PowerShell y ver&#xE1;s la lista de distros que tienes.</p>
</blockquote>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/05/13.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1114" height="449" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/05/13.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/05/13.png 1000w, https://www.wilsonvargas.com/content/images/2023/05/13.png 1114w" sizes="(min-width: 720px) 720px"></figure><p>Despu&#xE9;s de hacer esta configuraci&#xF3;n notar&#xE1;s que al iniciar la terminal de tu distro te est&#xE1; solicitando la contrase&#xF1;a de `root` esto porque en las sentencias que escribimos l&#xED;neas arriba se usa en comando `sudo`. Para hacer que no te pida contrase&#xF1;a al iniciar la terminal haremos lo siguiente:</p><p>Ejecutamos `sudo visudo` y dentro de este archivo agregamos la siguiente l&#xED;nea al final del archivo:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>%docker ALL=(ALL)  NOPASSWD: /usr/bin/dockerd</code>
</pre><!--kg-card-end: html--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/05/14.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1114" height="634" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/05/14.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/05/14.png 1000w, https://www.wilsonvargas.com/content/images/2023/05/14.png 1114w" sizes="(min-width: 720px) 720px"></figure><p>Guardamos y al abrir nuevamente nuestra terminal ya no volver&#xE1; a solicitarnos la clave.</p><h2 id="probando-docker">Probando Docker</h2><p>Una vez instalado y configurado Docker podemos probar y empezar a usarlo, para efectos de este tutorial vamos a ejecutar Nginx en Docker.</p><p>Primero descargamos la imagen de Nginx desde Docker Hub:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>docker pull nginx</code>
</pre><!--kg-card-end: html--><p>Y a continuaci&#xF3;n ejecutamos Nginx:</p><!--kg-card-begin: html--><pre class="line-numbers language-shell">
<code>docker run --name docker-nginx -p 80:80 nginx</code>
</pre><!--kg-card-end: html--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/05/15.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1117" height="626" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/05/15.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/05/15.png 1000w, https://www.wilsonvargas.com/content/images/2023/05/15.png 1117w" sizes="(min-width: 720px) 720px"></figure><p>Ahora podemos acceder desde nuestra maquina en Windows a nginx con la siguiente direcci&#xF3;n:<em> http://localhost:80</em></p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2023/05/16.png" class="kg-image" alt="C&#xF3;mo instalar Docker en Windows 11 (WSL) sin Docker Desktop" loading="lazy" width="1095" height="445" srcset="https://www.wilsonvargas.com/content/images/size/w600/2023/05/16.png 600w, https://www.wilsonvargas.com/content/images/size/w1000/2023/05/16.png 1000w, https://www.wilsonvargas.com/content/images/2023/05/16.png 1095w" sizes="(min-width: 720px) 720px"></figure><h2 id="conclusi%C3%B3n">Conclusi&#xF3;n</h2><p>Instalar Docker en Windows 11 sin usar Docker Desktop es posible gracias a la funci&#xF3;n de WSL2 que permite ejecutar una distribuci&#xF3;n de Linux dentro de Windows. Este ejercicio es especialmente &#xFA;til para aquellos desarrolladores (como yo :P ) que prefieren trabajar con herramientas de l&#xED;nea de comando o que quieren tener m&#xE1;s control sobre la instalaci&#xF3;n de Docker en su m&#xE1;quina. Nos leemos pronto :) </p>]]></content:encoded></item><item><title><![CDATA[Subiendo imágenes a un servidor desde una aplicación móvil usando Xamarin Forms]]></title><description><![CDATA[En este articulo vamos a ver la forma de como hacer todo ese procedimiento de almacenar las imágenes y que obtengamos una URL de la imagen.]]></description><link>https://www.wilsonvargas.com/subiendo-imagenes-xamarin-forms/</link><guid isPermaLink="false">6416018a25be7bc132b5eb79</guid><category><![CDATA[mobile]]></category><category><![CDATA[Xamarin]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Sun, 22 Mar 2020 18:43:03 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2020/03/file_upload.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2020/03/file_upload.png" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms"><p>Usualmente muchos de nuestros usuarios requieren que las aplicaciones que ellos usan puedan ser administrables, es decir, puedan cambiar su contrase&#xF1;a, nombre de usuario, etc. Pero sobre todo existe la necesidad de que cada usuario pueda usar su propia foto de perfil y para eso necesitamos poder subir y almacenar esas fotos en un servidor de almacenamiento.</p><p>En este articulo vamos a ver la forma de como hacer todo ese procedimiento de almacenar las im&#xE1;genes y que obtengamos una URL de la imagen para as&#xED; poder hacer referencia de ella en diferentes ocasiones. </p><p>Bueno, hoy usaremos una herramienta de Azure que nos ayudar&#xE1; con este prop&#xF3;sito, esta herramienta es una Cuenta de almacenamiento que nos permitir&#xE1; almacenar nuestras im&#xE1;genes r&#xE1;pida y f&#xE1;cil.</p><p>Para empezar con este tutorial ser&#xE1; necesario tener una cuenta de Azure, si a&#xFA;n no tienes una puedes<a href="https://azure.microsoft.com/es-es/free/?ref=wilsonvargas.com"> obtener una cuenta gratuita</a>.</p><h2 id="configurando-el-servidor">Configurando el servidor</h2><p>Lo primero que haremos ser&#xE1; crear un nuevo servicio en nuestro portal de Azure, para eso vamos a la opci&#xF3;n <strong>Crear un recurso</strong> y luego a <strong>Cuenta de almacenamiento</strong></p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2020/03/1.PNG" class="kg-image" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2020/03/2.PNG" class="kg-image" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms" loading="lazy"></figure><p>En esta secci&#xF3;n lo que vamos hacer es elegir nuestra suscripci&#xF3;n, grupo de recurso que vamos a usar; si t&#xFA; no tienes un grupo de recurso puedes crear uno; tambi&#xE9;n es importante elegir la ubicaci&#xF3;n de nuestro servicio.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2020/03/3.PNG" class="kg-image" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms" loading="lazy"></figure><p>Una vez creada nuestra cuenta de almacenamiento vamos a la secci&#xF3;n <strong>Contenedores </strong>y ah&#xED; vamos a agregar un nuevo contenedor, le damos un nombre apropiado en nuestro caso <strong>xamarin-blob</strong> (guarden este nombre que nos servir&#xE1; m&#xE1;s adelante)</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2020/03/4-1.PNG" class="kg-image" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms" loading="lazy"></figure><p>Finalmente, regresamos al resumen de nuestra cuenta de almacenamiento y nos dirigimos a la opci&#xF3;n <strong>Llaves de acceso</strong> aqu&#xED; vamos a obtener nuestra cadena de conexi&#xF3;n la cual usaremos m&#xE1;s adelante en nuestro proyecto.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2020/03/5-1.PNG" class="kg-image" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms" loading="lazy"></figure><p></p><h2 id="escribamos-c-digo">Escribamos c&#xF3;digo</h2><p>Crearemos un nuevo proyecto m&#xF3;vil multiplataforma en Visual Studio</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2020/03/6.PNG" class="kg-image" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms" loading="lazy"></figure><p>Lo primero que haremos ser&#xE1; agregar un par de paquetes nuget a nuestro proyecto. El primero ser&#xE1; <strong>WindowsAzure.Storage</strong> hacemos clic derecho en nuestra soluci&#xF3;n y luego en <strong>Administrar paquetes nuget para la soluci&#xF3;n</strong>. Buscamos el nombre de nuetro paquete ya mencionado y seleccionamos solo el proyecto principal.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2020/03/7.PNG" class="kg-image" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms" loading="lazy"></figure><p>Hacemos lo mismo para el siguiente paquete llamado <strong>Xam.Plugin.Media</strong></p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2020/03/8.PNG" class="kg-image" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms" loading="lazy"></figure><p>Ahora cambiaremos la estructura de nuestro proyecto y crearemos algunas clases necesarias para que finalmente nuestro proyecto quede de la siguiente manera:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2020/03/9.PNG" class="kg-image" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms" loading="lazy"></figure><p>Iniciemos por la carpeta <strong>Services</strong>. Creamos una clase llamada <strong>BlobServices.cs</strong> y agregamos el m&#xE9;todo <strong>Upload</strong></p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/cb5ae61526bc32f3920f0c51d747b89b.js"></script><!--kg-card-end: html--><p>Ahora encapsularemos esta clase para que sea dependiente de una interface, para eso vamos a seleccionar el nombre de la clase y hacer clic derecho, luego <strong>Acciones r&#xE1;pidas y refactorizaci&#xF3;n</strong>, a continuaci&#xF3;n, clic en <strong>Extraer en una interface</strong> y finalmente seleccionar el m&#xE9;todo que queremos encapsular.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2020/03/10.png" class="kg-image" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms" loading="lazy"></figure><p>A continuaci&#xF3;n, iremos a la carpeta <strong>ViewModels </strong>y en la clase <strong>MainViewModel.cs</strong> agregaremos los siguientes m&#xE9;todos:</p><p><strong>PickPicture:</strong> Este m&#xE9;todo nos permitir&#xE1; elegir una foto de nuestra galer&#xED;a para poder subirla.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/967c2583d09611bf30ab20fe72103fa3.js"></script><!--kg-card-end: html--><p><strong>TakePicture</strong>: Este m&#xE9;todo nos permitir&#xE1; tomar una foto con la c&#xE1;mara del dispositivo para poder suburla.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/ffe97aaae50e4de8bda063d7e360d2ff.js"></script><!--kg-card-end: html--><p><strong>Upload:</strong> Este m&#xE9;todo nos permitir&#xE1; subir la foto que hemos tomado o elegido de la galer&#xED;a.</p><!--kg-card-begin: html--><pre class="line-numbers language-csharp">
<code>private async Task Upload()
{
    IsBusy = true;
    ResponseUrl = await blobServices.Upload(Path);
    IsBusy = false;
}</code>
</pre>
<!--kg-card-end: html--><p>Ahora vamos a ir a la carpeta <strong>Views </strong>y escribiremos la vista que nos mostrar&#xE1; la aplicaci&#xF3;n. B&#xE1;sicamente son 3 botones, un panel para mostrar la foto y una etiqueta para mostrar al final la URL que retorna la cuenta de almacenamiento en Azure.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/1172d4e4ae75d577d8a54b9968478043.js"></script><!--kg-card-end: html--><h2 id="ajustes-finales">Ajustes finales</h2><p>En ambas plataformas necesitamos que el usuario otorgue permisos a nuestra aplicaci&#xF3;n para poder acceder a la galer&#xED;a o para poder hacer uso de la c&#xE1;mara.</p><h3 id="android">Android</h3><p>En Android crearemos una sub carpeta en la carpeta <strong>Resources</strong> llamada <strong>xml</strong>, dentro de esta carpeta crearemos un archivo llamado &#xA0;<strong>file_paths.xml</strong> y escribiremos lo siguiente:</p><!--kg-card-begin: markdown--><pre><code class="language-xml">&lt;paths xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;
    &lt;external-files-path name=&quot;my_images&quot; path=&quot;Pictures&quot; /&gt;
    &lt;external-files-path name=&quot;my_movies&quot; path=&quot;Movies&quot; /&gt;
&lt;/paths&gt;
</code></pre>
<!--kg-card-end: markdown--><p>Paso siguiente ser&#xE1; agregar la siguiente l&#xED;nea de c&#xF3;digo en la clase <strong>MainActivity.cs</strong> dentro del m&#xE9;todo <strong>OnCreate</strong></p><!--kg-card-begin: markdown--><pre><code class="language-csharp">CrossCurrentActivity.Current.Init(this, savedInstanceState);
</code></pre>
<!--kg-card-end: markdown--><h3 id="ios">iOS</h3><p>En iOS lo &#xFA;nico que haremos ser&#xE1; agregar el siguiente c&#xF3;digo a nuestro archivo <strong>Info.plist</strong></p><!--kg-card-begin: markdown--><pre><code>&lt;key&gt;NSCameraUsageDescription&lt;/key&gt;
&lt;string&gt;This app needs access to the camera to take photos.&lt;/string&gt;
&lt;key&gt;NSPhotoLibraryUsageDescription&lt;/key&gt;
&lt;string&gt;This app needs access to photos.&lt;/string&gt;
&lt;key&gt;NSMicrophoneUsageDescription&lt;/key&gt;
&lt;string&gt;This app needs access to microphone.&lt;/string&gt;
&lt;key&gt;NSPhotoLibraryAddUsageDescription&lt;/key&gt;
&lt;string&gt;This app needs access to the photo gallery.&lt;/string&gt;
</code></pre>
<!--kg-card-end: markdown--><p>Finalmente, obtendremos lo siguiente:</p><figure class="kg-card kg-image-card kg-width-full"><img src="https://www.wilsonvargas.com/content/images/2020/03/11-1.PNG" class="kg-image" alt="Subiendo im&#xE1;genes a un servidor desde una aplicaci&#xF3;n m&#xF3;vil usando Xamarin Forms" loading="lazy"></figure><h2 id="conclusi-n">Conclusi&#xF3;n</h2><p>En este tutorial vimos una manera f&#xE1;cil y sencilla de subir y almacenar im&#xE1;genes desde una aplicaci&#xF3;n hecha con Xamarin Forms, se los servicios de Cuentas de Almacenamiento de Azure y se logr&#xF3; con el resultado esperado. Recuerden que el proyecto completo est&#xE1; en mi <a href="https://github.com/wilsonvargas?ref=wilsonvargas.com">cuenta de GitHub</a>, lo pueden descargar modificar, pero sobre todo aprender de &#xE9;l. Nos leemos pronto &#x1F60A;</p>]]></content:encoded></item><item><title><![CDATA[Mostrar el valor del campo contraseña en Xamarin.Forms... Sin usar Custom Renderers]]></title><description><![CDATA[En este articulo veremos una técnica para darle la facilidad al usuario de mostrar el valor del campo contraseña sin usar Custom Renderers.]]></description><link>https://www.wilsonvargas.com/mostrar-el-valor-del-campo-contrasena-en-xamarin-forms/</link><guid isPermaLink="false">6416018a25be7bc132b5eb78</guid><category><![CDATA[Xamarin]]></category><category><![CDATA[mobile]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Sat, 17 Aug 2019 00:23:00 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2019/08/cover.PNG" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2019/08/cover.PNG" alt="Mostrar el valor del campo contrase&#xF1;a en Xamarin.Forms... Sin usar Custom Renderers"><p>Muchas veces nuestros usuarios necesitan ver la informaci&#xF3;n que est&#xE1;n usando como input en nuestras aplicaciones. Ese es el caso del campo contrase&#xF1;a, que por ser un campo con un cierto grado de seguridad no muestra su valor actual.</p><p>En este articulo veremos una t&#xE9;cnica para darle la facilidad al usuario de mostrar el valor del campo contrase&#xF1;a sin usar Custom Renderers.</p><h2 id="el-contexto">El contexto</h2><p>Como sabr&#xE1;n muchas de las aplicaciones hoy en d&#xED;a necesitan tener autenticaci&#xF3;n de usuarios y para eso es necesario tener una pantalla de autenticaci&#xF3;n, o pantalla de login, en estos casos es muy com&#xFA;n ver el campo contrase&#xF1;a con un peque&#xF1;o bot&#xF3;n donde le indica al usuario que tocando en esa parte ellos pueden ver el valor que est&#xE1;n ingresando.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/08/image1.png" class="kg-image" alt="Mostrar el valor del campo contrase&#xF1;a en Xamarin.Forms... Sin usar Custom Renderers" loading="lazy"></figure><h2 id="es-un-control-extra-o-ah-usemos-custom-renderers-">Es un control extra&#xF1;o, ah usemos Custom Renderers!</h2><p>Muchas veces lo primero que se nos viene a la mente cuando vemos controles un poco personalizados es usar <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/?ref=wilsonvargas.com">Custom Renderers</a>, de hecho, a mi parecer es lo mejor que ha podido crear Xamarin.Forms para poder lidiar con controles que son un poco peculiares y no se pueden conseguir con usando los controles compartidos.</p><p>Sin embargo, no es necesario usar Custom Renderers en todos los casos, muchas veces la plataforma nos ofrece formas de hacer nuestros controles sin la necesidad de usar esta caracter&#xED;stica y este es el caso.</p><h2 id="a-codificar-">A codificar!</h2><p>Bien, lo primero que haremos es crear nuestro control. Es una manera muy sencilla de solo usar XAML, de hecho, solo haremos una superposici&#xF3;n de controles:</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/090cb5e7260e1d98793d3690e14d80b7.js"></script><!--kg-card-end: html--><p>Como veras, mi control principal es un <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/grid?ref=wilsonvargas.com">Grid </a>que contiene un <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/text/entry?ref=wilsonvargas.com">Entry </a>que ser&#xE1; el core de nuestro control por as&#xED; decirlo, y otro Grid alineado al lado derecho que contiene, a su vez, dos controles m&#xE1;s que son el bot&#xF3;n que usamos para mostrar u ocultar el valor de nuestro Entry y la imagen que muestra un icono.</p><p>Finalmente, usamos una propiedad llamada <a href="https://docs.microsoft.com/en-us/dotnet/api/xamarin.forms.layout.isclippedtobounds?view=xamarin-forms&amp;ref=wilsonvargas.com">IsClippedToBounds </a>para recortar las &#xE1;reas de los elementos internos del Grid, para que todo se junte como un solo elemento en la interfaz de usuario.</p><h2 id="y-c-mo-controlamos-las-acciones">Y c&#xF3;mo controlamos las acciones?</h2><p>En el paso anterior creamos un control el cual tendr&#xED;a que cambiar una propiedad de nuestro Entry para poder visualizar su valor.</p><p>Bueno, es momento de usar un <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/triggers?ref=wilsonvargas.com">Trigger</a>&#x2026; Con esto nosotros podremos agregar acciones a los controles escrito en XAML, permitiendo realizar cambios de apariencia en ellos cuando una determinada acci&#xF3;n ocurre. Exactamente lo que queremos hacer en este caso &#x1F601;</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/f2131d70183e9043e95a8e1f7d30ebfd.js"></script><!--kg-card-end: html--><p>Como ver&#xE1;s esta parte es sencilla, solo es decirle a nuestro Trigger que cuando se invoque cambie la propiedad IsPassword de nuestro Entry y cambiar la imagen del icono que estamos mostrando.</p><p>Lo siguiente es agregar el trigger al control en el cual queremos que se lance, entonces lo agregaremos al control Button de la siguiente manera:</p><!--kg-card-begin: markdown--><pre><code>&lt;Button.Triggers&gt;
    &lt;EventTrigger Event=&quot;Clicked&quot;&gt;
        &lt;triggers:ShowPasswordTriggerAction
            EntryPasswordName=&quot;EntryPassword&quot;
            IconImageName=&quot;ShowPasswordButtonIcon&quot; /&gt;
    &lt;/EventTrigger&gt;
&lt;/Button.Triggers&gt;
</code></pre>
<!--kg-card-end: markdown--><p>Finalmente tendremos lo que necesitamos&#x2026;</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/08/1.gif" class="kg-image" alt="Mostrar el valor del campo contrase&#xF1;a en Xamarin.Forms... Sin usar Custom Renderers" loading="lazy"></figure><!--kg-card-begin: html--><div style="display: table;">
  <div style="float: left; width: 50%; padding: 5px;">
    <img src="https://www.wilsonvargas.com/content/images/2019/08/android.png" style="width:100%" alt="Mostrar el valor del campo contrase&#xF1;a en Xamarin.Forms... Sin usar Custom Renderers">
  </div>
  <div style="float: left; width: 50%; padding: 5px;">
    <img src="https://www.wilsonvargas.com/content/images/2019/08/ios.png" style="width:100%" alt="Mostrar el valor del campo contrase&#xF1;a en Xamarin.Forms... Sin usar Custom Renderers">
  </div>  
</div><!--kg-card-end: html--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/08/uwp.png" class="kg-image" alt="Mostrar el valor del campo contrase&#xF1;a en Xamarin.Forms... Sin usar Custom Renderers" loading="lazy"></figure><h2 id="conclusi-n">Conclusi&#xF3;n</h2><p>Como pudiste ver en este articulo te muestro como mostrar el valor de un campo contrase&#xF1;a sin la necesidad de usar Custom Renderers, que de por s&#xED; son muy &#xFA;tiles para crear controles personalizados, pero no hay que abusar mucho de ellos. Tambi&#xE9;n aprendimos, de manera muy r&#xE1;pida, como usar los Triggers y cuando debemos utilizarlos.</p><p>Recuerda que el c&#xF3;digo fuente de este ejemplo lo puedes obtener en mi cuenta de <a href="https://github.com/wilsonvargas/ShowPasswordXamarin?ref=wilsonvargas.com">Github</a>, nos leemos pronto &#x1F609;</p>]]></content:encoded></item><item><title><![CDATA[Ocultando la información de una aplicación suspendida en Xamarin.Forms]]></title><description><![CDATA[En este tutorial te enseño de manera sencilla a cómo ocultar la información de una aplicación suspendida en Xamarin.Forms. ]]></description><link>https://www.wilsonvargas.com/ocultando-la-informacion-de-una-aplicacion-suspendida-en-xamarin-forms/</link><guid isPermaLink="false">6416018a25be7bc132b5eb77</guid><category><![CDATA[mobile]]></category><category><![CDATA[Xamarin]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Sat, 13 Jul 2019 18:55:20 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2019/07/Untitled-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2019/07/Untitled-1.png" alt="Ocultando la informaci&#xF3;n de una aplicaci&#xF3;n suspendida en Xamarin.Forms"><p>Cuando una aplicaci&#xF3;n pasa a un estado de suspensi&#xF3;n, es decir, cuando es reemplazada por otra aplicaci&#xF3;n y esta pasa a segundo plano, el sistema operativo toma una captura de pantalla y la usa como una vista previa de la aplicaci&#xF3;n.</p><p>En algunas ocasiones nosotros queremos ocultar informaci&#xF3;n sensible de la aplicaci&#xF3;n, pero una manera de acceder; no por completo, pero s&#xED; parcialmente, es de la forma explicada arriba.</p><p>Para ser mas claro, la aplicaci&#xF3;n de WhatsApp &#xFA;ltimamente incluy&#xF3; la caracter&#xED;stica de usar la autenticaci&#xF3;n con huella para acceder a la aplicaci&#xF3;n, esto porque en muchos casos los usuarios no quieren que puedan ver sus conversaciones. </p><p>Sin embargo, cuando la aplicaci&#xF3;n pasaba a segundo plano y us&#xE1;bamos el administrador de tareas del tel&#xE9;fono, se pod&#xED;a ver parte de algunas de sus ultimas conversaciones, debido a que el sistema operativo por defecto toma una captura de lo &#xFA;ltimo que hiciste en la aplicaci&#xF3;n y la usa como vista principal, este error ha sido solucionado y ahora se ve de esta manera:</p><!--kg-card-begin: html--><center><img src="https://www.wilsonvargas.com/content/images/2019/07/image1-1.png" alt="Ocultando la informaci&#xF3;n de una aplicaci&#xF3;n suspendida en Xamarin.Forms" style="width:480px; height:800px;"></center><!--kg-card-end: html--><p>En este ejemplo se muestra claramente como WhatsApp protege la informaci&#xF3;n cuando la aplicaci&#xF3;n entra en estado de suspensi&#xF3;n.</p><p>Tal vez no sea una caracter&#xED;stica tan importante en la mayor&#xED;a de aplicaciones, pero en casos puntuales es un requerimiento esencial.</p><p>Supongamos que creamos una aplicaci&#xF3;n de autenticaci&#xF3;n donde en la pantalla principal se muestra todos c&#xF3;digos que uso para acceder a mis cuentas. Nuestra aplicaci&#xF3;n ser&#xED;a vulnerable al momento de entrar en estado de suspensi&#xF3;n porque los c&#xF3;digos podr&#xED;an ser vistos al momento de cambiar de aplicaci&#xF3;n.</p><h2 id="manos-al-c-digo">Manos al c&#xF3;digo</h2><p>Bueno, esta vez no ser&#xE9; muy enf&#xE1;tico en mostrar como crear un proyecto ni en el dise&#xF1;o que va a tener, sino simplemente en como ocultar la informaci&#xF3;n cuando nuestra aplicaci&#xF3;n entra en estado de suspensi&#xF3;n. Igual tu puedes ver el c&#xF3;digo fuente de este post en mi cuenta de <a href="https://github.com/wilsonvargas/XamarinSecureBackground?ref=wilsonvargas.com">GitHub</a></p><p>Haremos el ejemplo de una aplicaci&#xF3;n que almacena c&#xF3;digos de autenticaci&#xF3;n, imaginemos que en esta aplicaci&#xF3;n est&#xE1; guardados los c&#xF3;digos para acceder a nuestra cuenta de Facebook, de Github, de tu correo etc.</p><p>Nuestra aplicaci&#xF3;n cuando la usamos se ve de la siguiente manera.</p><!--kg-card-begin: html--><div style="display: table;">
  <div style="float: left; width: 50%; padding: 5px;">
    <img src="https://www.wilsonvargas.com/content/images/2019/07/image2.png" style="width:100%" alt="Ocultando la informaci&#xF3;n de una aplicaci&#xF3;n suspendida en Xamarin.Forms">
  </div>
  <div style="float: left; width: 50%; padding: 5px;">
    <img src="https://www.wilsonvargas.com/content/images/2019/07/image5.png" style="width:100%" alt="Ocultando la informaci&#xF3;n de una aplicaci&#xF3;n suspendida en Xamarin.Forms">
  </div>  
</div><!--kg-card-end: html--><p></p><p>Pero cuando pasa a estado de suspensi&#xF3;n, es decir a segundo plano, se ve de la siguiente manera:</p><!--kg-card-begin: html--><div style="display: table;">
  <div style="float: left; width: 50%; padding: 5px;">
    <img src="https://www.wilsonvargas.com/content/images/2019/07/Screenshot_1562988836.png" style="width:100%" alt="Ocultando la informaci&#xF3;n de una aplicaci&#xF3;n suspendida en Xamarin.Forms">
  </div>
  <div style="float: left; width: 50%; padding: 5px;">
    <img src="https://www.wilsonvargas.com/content/images/2019/07/imag33.png" style="width:100%" alt="Ocultando la informaci&#xF3;n de una aplicaci&#xF3;n suspendida en Xamarin.Forms">
  </div>  
</div><!--kg-card-end: html--><p>Obviamente no queremos que nuestros c&#xF3;digos de autenticaci&#xF3;n se vean de esta manera, para eso vamos a escribir un poco de c&#xF3;digo&#x2026;</p><h2 id="xamarin-ios">Xamarin.iOS</h2><p>En nuestro proyecto <strong>Xamarin.iOS</strong> hay varias maneras de hacerlo, la m&#xE1;s f&#xE1;cil es poner un fondo blanco a nuestra ventana principal, pero a mi parecer eso no es muy agradable para el usuario.</p><p>En lugar de eso vamos a usar la imagen de SplashScreen para ponerla cuando nuestra aplicaci&#xF3;n est&#xE1; en suspensi&#xF3;n. Para eso en el archivo <strong><em>AppDelegate.cs</em></strong> y agregaremos dos m&#xE9;todos sobrescritos.</p><p>El primero ser&#xE1; <em><strong>OnResignActivation </strong></em>este m&#xE9;todo es llamado cuando la aplicaci&#xF3;n est&#xE1; a punto de suspenderse o cuando el usuario tiene alguna interrupci&#xF3;n, como una llamada o un mensaje de texto.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/514a783d9df7fce9c3e4d032f13d588f.js"></script><!--kg-card-end: html--><p>Aqu&#xED; lo que haremos es obtener la imagen de nuestro SplashScreen y convertirla en tipo <strong>UIImageView</strong>, luego de eso simplemente la agregamos a la vista principal de nuestra aplicaci&#xF3;n.</p><p>Ahora sobrescribimos el segundo m&#xE9;todo, <strong><em>OnActivated </em></strong>este m&#xE9;todo es llamado cuando se inicia la aplicaci&#xF3;n y cada vez que la aplicaci&#xF3;n vuelve al primer plano. Aqu&#xED; solo quitaremos la imagen de nuestra vista principal.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/898a666361483f4cc1344334c161ef49.js"></script><!--kg-card-end: html--><p>Finalmente, cuando nuestra aplicaci&#xF3;n entra en estado de suspensi&#xF3;n se ve de la siguiente manera:</p><!--kg-card-begin: html--><center><img src="https://www.wilsonvargas.com/content/images/2019/07/image7.png" alt="Ocultando la informaci&#xF3;n de una aplicaci&#xF3;n suspendida en Xamarin.Forms" style="width:480px; height:800px;"></center><!--kg-card-end: html--><h2 id="xamarin-android">Xamarin.Android</h2><p>En Android, realmente no existe algo para hacer una superposici&#xF3;n, as&#xED; como lo hice en iOS, o al menos no que yo sepa &#x1F923; . Si t&#xFA; conoces de alguna manera de hacer lo mismo que hicimos en Xamarin.iOS pues si&#xE9;ntete libre de enviar un PR a este proyecto o escribirme un tweet diciendo c&#xF3;mo se hacerlo.</p><p>Para este caso vamos a ir al archivo <em><strong>MainActivity.cs</strong></em>, en el m&#xE9;todo <strong><em>OnCreate</em></strong>, simplemente agregamos la bandera <em>WindowManagerFlags.Secure</em>, esto tenemos que hacerlo despu&#xE9;s que se haya cargado Xamarin.Forms, para asegurarnos de que no se sobrescriba.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/4805df6d2cb3706ec843733b94819728.js"></script><!--kg-card-end: html--><p>Finalmente, cuando nuestra aplicaci&#xF3;n entra en estado de suspensi&#xF3;n se ve de la siguiente manera:</p><!--kg-card-begin: html--><center><img src="https://www.wilsonvargas.com/content/images/2019/07/image4.png" alt="Ocultando la informaci&#xF3;n de una aplicaci&#xF3;n suspendida en Xamarin.Forms" style="width:480px; height:800px;"></center><!--kg-card-end: html--><h2 id="conclusi-n">Conclusi&#xF3;n</h2><p>Muchas aplicaciones necesitan ocultar informaci&#xF3;n dentro de ellas, pero cuando entra en estado de suspensi&#xF3;n el sistema operativo toma una captura de pantalla y la pone como vista principal.</p><p>En este articulo explicamos como ocultar dicha informaci&#xF3;n cuando nuestra aplicaci&#xF3;n entra en estado de suspensi&#xF3;n, recuerda que puedes encontrar el c&#xF3;digo fuente de este ejemplo en mi cuenta de <a href="https://github.com/wilsonvargas/XamarinSecureBackground?ref=wilsonvargas.com">Github</a>. Nos leemos pronto! &#x1F60A;</p>]]></content:encoded></item><item><title><![CDATA[Creando un StackLayout dinámico en Xamarin.Forms]]></title><description><![CDATA[Tutorial que explica de una manera sencilla de crear un StackLayout dinámico para Xamarin.Forms.]]></description><link>https://www.wilsonvargas.com/creando-un-stacklayout-dinamico-en-xamarin-forms/</link><guid isPermaLink="false">6416018a25be7bc132b5eb6f</guid><category><![CDATA[Xamarin]]></category><category><![CDATA[mobile]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Mon, 12 Nov 2018 23:34:00 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2019/05/xamarin.forms-bindable.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2019/05/xamarin.forms-bindable.png" alt="Creando un StackLayout din&#xE1;mico en Xamarin.Forms"><p>Hola, hoy en el trabajo a mi amigo Manuel Ruh (el buen Rugue &#x1F60A;) y a m&#xED; se nos present&#xF3; un problema con la estructura de una vista en Xamarin. Los pongo en contexto:</p><p>La aplicaci&#xF3;n tiene una vista en donde tiene un <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/layouts/scroll-view?ref=wilsonvargas.com">ScrollView </a>como uno de los contenedores principales, el cual tiene una animaci&#xF3;n al hacer scroll o deslizar hacia arriba. Entonces, es necesario tener el ScrollView como un contenedor principal.</p><p>Dentro de este ScrollView tenemos varios controles, como contenedores, botones, labels, etc. Pero adicionalmente era necesario agregar un <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/?ref=wilsonvargas.com">ListView</a>, entonces naci&#xF3; el problema!</p><p>Por definici&#xF3;n general no se puede o no es recomendable poner un ListView dentro de un ScrollView debido a que el control ListView tiene su propio ScrollView por defecto, esto no arroja ning&#xFA;n error de compilaci&#xF3;n, ni una excepci&#xF3;n. Xamarin a manera de que no haya problemas al momento de hacer scroll en dos controles similares lo que hace es darle por defecto la altura m&#xE1;xima al ListView y esto hace que el ListView a pesar de tener dos elementos en la lista se vea demasiado grande ocupando mucho espacio en la pantalla del dispositivo.</p><p>Algo parecido a esta vista:</p><blockquote>Esta no es la vista que usamos, pero ten&#xED;amos ese mismo problema.</blockquote><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/image1.png" class="kg-image" alt="Creando un StackLayout din&#xE1;mico en Xamarin.Forms" loading="lazy"></figure><h2 id="san-google-lo-hizo-de-nuevo-">San Google lo hizo de nuevo!</h2><p>La primera idea que se nos vino a la cabeza es crear un <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/?ref=wilsonvargas.com">CustomRenderer </a>en el cual podamos deshabilitar el scroll del ListView, tal vez eso pod&#xED;a solucionar el problema, pero no funcion&#xF3; xdxd &#x1F605;&#x1F605;</p><p>Luego nos pusimos a ver algo en Google de como podr&#xED;amos deshabilitar el scroll y discut&#xED;amos sobre que Xamarin.Forms deber&#xED;a tener la forma de deshabilitar esta propiedad habiendo tanta gente que necesita este feature.</p><p>Afortunadamente, nos topamos con un pedido de algo similar a lo que nosotros necesit&#xE1;bamos, este pedido lo hizo *Jeremy Wesley*, curiosamente en el 2016 &#x1F631;&#x1F631; y SORPRESIVAMENTE ten&#xED;a una respuesta que se hab&#xED;a hecho en Setiembre de este a&#xF1;o, ESTE A&#xD1;O (2018)!!</p><p>B&#xE1;sicamente, la respuesta era que ya hab&#xED;an agregado esta caracter&#xED;stica en la versi&#xF3;n 3.1.0 de Xamarin.Forms pero al control ScrollView.</p><p>Peeero le dejo una recomendaci&#xF3;n de usar un control llamado **RepeaterView** y dej&#xF3; el enlace de la issue registrada en Github, la cual fue solucionada en un excelente Pull Request &#x1F60D;&#x1F60D;&#x1F60D;</p><p>Te lo dejo aqu&#xED;: <br><br><a href="https://github.com/xamarin/Xamarin.Forms/pull/4052?ref=wilsonvargas.com">Bindable layouts</a></p><h2 id="encontrando-la-soluci-n">Encontrando la soluci&#xF3;n</h2><p>Hab&#xED;amos visto que el Pull Request se hab&#xED;a integrado a la rama principal y la primera opci&#xF3;n era actualizar Xamarin.Forms pero vimos que a&#xFA;n no hab&#xED;a sido agregado al paquete publicado en nuget entonces qued&#xF3; descartada esa opci&#xF3;n.</p><p>Pero gracias a ese Pull Request nos dio la idea b&#xE1;sica de crear un control personalizado que tenga las propiedades de ItemsSource e ItemTemplate.</p><p>El control personalizado a manera general extiende de StackLayout y tiene dos BindableProperties.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/3f5c2c7fe8b34db92cf7a9817e5faa1d.js"></script><!--kg-card-end: html--><p>Estas dos BindableProperties son las mismas que tiene un ListView, esto hace pueda recibir una lista de objetos y poder renderizarlas conforme a lo que declaras en el ItemTemplate.</p><p>Desde XAML podemos llamarlo como si fuese un ListView pero con todas las propiedades de un StackLayout, cual es la ventaja de eso? Que puedes crear un ListView con scroll horizontal solo cambiando la propiedad Orientation.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/c9ebdc467b2aea57f4054c4a1158f1ba.js"></script><!--kg-card-end: html--><p>Finalmente, en el dispositivo se ve as&#xED;:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/image2.png" class="kg-image" alt="Creando un StackLayout din&#xE1;mico en Xamarin.Forms" loading="lazy"></figure><h2 id="conclusi-n-">Conclusi&#xF3;n:</h2><p>Bueno, lo que vimos hoy fue un problema que sin saberlo le pasaba a varios y que ven&#xED;a siendo pedido como caracter&#xED;stica desde el a&#xF1;o 2016. Que en s&#xED; era poner un ListView dentro de un ScrollView con la posibilidad de deshabilitar la funci&#xF3;n de Scroll. Vimos que esta ser&#xE1; una caracter&#xED;stica que se agregar&#xE1; en la pr&#xF3;xima versi&#xF3;n de Xamarin.Forms (3.4), solo para terminar quiero hacer &#xE9;nfasis en lo bueno que es hacer contribuciones de c&#xF3;digo abierto, esto ayuda a que la plataforma se haga mas robusta y que muchos desarrolladores puedan usar las nuevas caracter&#xED;sticas.</p><p>Aqu&#xED; tienes el repositorio oficial de <a href="https://github.com/xamarin/Xamarin.Forms?ref=wilsonvargas.com">Xamarin.Forms</a>, puedes ver que es de c&#xF3;digo abierto, es decir, puedes ver, aprender y modificar el c&#xF3;digo t&#xFA; mismo.</p><p>Nos leemos pronto &#x1F609;&#x1F609;</p>]]></content:encoded></item><item><title><![CDATA[Usando estilos CSS Xamarin.Forms]]></title><description><![CDATA[En este tutorial explico como se utiliza archivos CSS para dar estilos a nuestras aplicaciones con Xamarin.Forms y cuales son las limitaciones de momento.]]></description><link>https://www.wilsonvargas.com/usando-estilos-css-xamarin-forms/</link><guid isPermaLink="false">6416018a25be7bc132b5eb70</guid><category><![CDATA[Xamarin]]></category><category><![CDATA[mobile]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Tue, 12 Jun 2018 00:14:00 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2019/05/ext.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2019/05/ext.jpg" alt="Usando estilos CSS Xamarin.Forms"><p>Hola, hace poco fue el Build 2018, el mayor evento para desarrolladores de Microsoft como se es de esperar en este evento hace lanzamientos de novedades y mejoras en sus plataformas. A mi parecer este a&#xF1;o el protagonista fue la Inteligencia Artificial.</p><p>Empezando por el acuerdo de Microsoft y DJI para llevar el machine learning a los drones de esa compa&#xF1;&#xED;a, el lanzamiento de Project Kinect for Azure y lo mejor de todo (a mi parecer) <strong>IA for Accessibility</strong> un programa que busca aprovechar la IA para crear un mundo m&#xE1;s accesible para las personas con discapacidad.</p><p>En fin, hay muchos lanzamientos s&#xFA;per buenos en todo el entorno Microsoft. Si te perdiste el evento y los lanzamientos los puedes volver a ver aqu&#xED;:</p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/rd0Rd8w3FZ0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"></iframe><!--kg-card-end: html--><p>Por el lado de Xamarin, finalmente lanzaron la versi&#xF3;n 3.0 de Xamarin.Forms la cual trae muchas mejoras y novedades, a mi parecer Xamarin est&#xE1; mejorando cada vez m&#xE1;s y esto hace que la pasi&#xF3;n que tengo por desarrollar en esta plataforma crezca conforme pasa el tiempo.</p><p>Si t&#xFA; quieres ver las novedades de Xamarin en el Build puedes echarle un ojo a este art&#xED;culo.</p><blockquote><a href="https://devblogs.microsoft.com/xamarin/xamarin-forms-3-0-released/?ref=wilsonvargas.com">Try the Latest Release of Xamarin.Forms 3.0!</a></blockquote><p>Hoy vamos hablar de una de ellas, seguro ya viste el titulo as&#xED; que ya te imaginaras cual es&#x2026; S&#xED;, con Xamarin.Forms 3.0 tenemos la posibilidad de integrar archivos CSS para los estilos de nuestra aplicaci&#xF3;n, adem&#xE1;s de la forma ya existente de agregar estilos a nuestra aplicaci&#xF3;n mediante XAML.</p><p>Para ser sinceros esta nueva forma de estilos parecido a las aplicaciones Web, es decir con archivos CSS, viene desde la versi&#xF3;n 2.6 en la rama <a href="https://github.com/xamarin/Xamarin.Forms/wiki/Nightly-Builds?ref=wilsonvargas.com">nightly </a>de Xamarin.Forms, pero no estaba tan estable como en la versi&#xF3;n 3.0 y no se sab&#xED;a si iba a ser una caracter&#xED;stica de esta nueva versi&#xF3;n.</p><h1 id="aclaraciones">Aclaraciones</h1><p>Para empezar, deben saber que esta forma de usar estilos con archivos CSS a&#xFA;n est&#xE1; en una versi&#xF3;n no tan madura por consiguiente no podemos hacer algo m&#xE1;s avanzando como con los archivos CSS en entornos Web.</p><p>Como esta forma de dar estilos a nuestras aplicaciones est&#xE1; en sus inicios es necesario saber que algunas propiedades y Selectors no son soportados, para tener m&#xE1;s claro este tema te dejo dos tablas en las cuales explica que elementos soporta Xamarin.Forms 3.0.</p><h2 id="selectors-">Selectors:</h2><!--kg-card-begin: html--><table id="tablepress-7" class="tablepress tablepress-id-7">
<thead>
<tr style="background-color:#D9EDF7;">
	<th class="column-1">Selector</th><th class="column-2">Ejemplo</th><th class="column-3">Descripci&#xF3;n</th>
</tr>
</thead>
<tbody>
<tr class="row-2 even">
	<td class="column-1">.class</td><td class="column-2">.className</td><td class="column-3">Establece la propiedad StyleClass en el elemento. Ej. .className</td>
</tr>
<tr class="row-3 odd">
	<td class="column-1">#id</td><td class="column-2">#emailEntry</td><td class="column-3">Verifica el StyleId, o a alternativas como: x:Name</td>
</tr>
<tr class="row-4 even">
	<td class="column-1">*</td><td class="column-2">*</td><td class="column-3">Para todos lo elementos</td>
</tr>
<tr class="row-5 odd">
	<td class="column-1">element</td><td class="column-2">entry</td><td class="column-3">Selecciona todos los elementos de tipo Entry, pero no las subclases.</td>
</tr>
<tr class="row-6 even">
	<td class="column-1">^base</td><td class="column-2">^contentpage</td><td class="column-3">Cualquier elemento como clase base, incluy&#xE9;ndose a s&#xED; mismo. Este es un selector espec&#xED;fico de XF.</td>
</tr>
<tr class="row-7 odd">
	<td class="column-1">element,element</td><td class="column-2">label, entry</td><td class="column-3">Seleccinado de multiples elementos</td>
</tr>
<tr class="row-8 even">
	<td class="column-1">element element</td><td class="column-2">contentview label</td><td class="column-3">Cualquier Label dentro de un ContentView</td>
</tr>
<tr class="row-9 odd">
	<td class="column-1">element&gt;element</td><td class="column-2">contentview&gt;label</td><td class="column-3">Selecciona Labels, que son hijo directo de un ContentView</td>
</tr>
<tr class="row-10 even">
	<td class="column-1">element+element</td><td class="column-2">label+entry</td><td class="column-3">Todos los elementos Entry que vienen despu&#xE9;s de un Label.</td>
</tr>
<tr class="row-11 odd">
	<td class="column-1">element~element</td><td class="column-2">label~entry</td><td class="column-3">Todas los Entries que tengan un Label delante.</td>
</tr>
</tbody>
</table><!--kg-card-end: html--><p>Los siguientes selectors no son soportados:</p><!--kg-card-begin: markdown--><pre><code>[attribute]    
@media y @supports    
: y ::
</code></pre>
<!--kg-card-end: markdown--><h2 id="propiedades">Propiedades</h2><!--kg-card-begin: html--><table id="tablepress-8" class="tablepress tablepress-id-8">
<thead style="background-color:#D9EDF7;">
<tr class="row-1 odd">
	<th class="column-1">Propiedad</th><th class="column-2">Aplica a</th><th class="column-3">Ejemplo</th>
</tr>
</thead>
<tbody>
<tr class="row-2 even">
	<td class="column-1">background-color</td><td class="column-2">VisualElement</td><td class="column-3">background-color:blue;</td>
</tr>
<tr class="row-3 odd">
	<td class="column-1">background-image</td><td class="column-2">Page</td><td class="column-3">background-image:image.png</td>
</tr>
<tr class="row-4 even">
	<td class="column-1">border-color</td><td class="column-2">Button, Frame</td><td class="column-3">border-color:#7aff67; (see colors)</td>
</tr>
<tr class="row-5 odd">
	<td class="column-1">border-width</td><td class="column-2">Button</td><td class="column-3">border-width:1;</td>
</tr>
<tr class="row-6 even">
	<td class="column-1">color</td><td class="column-2">Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, TimePicker</td><td class="column-3">color:#447799; (see colors)</td>
</tr>
<tr class="row-7 odd">
	<td class="column-1">direction</td><td class="column-2">VisualElement</td><td class="column-3">direction:rtl;</td>
</tr>
<tr class="row-8 even">
	<td class="column-1">font-family</td><td class="column-2">Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, TimePicker, Span</td><td class="column-3">font-family:&apos;Open Sans&apos;;</td>
</tr>
<tr class="row-9 odd">
	<td class="column-1">font-size</td><td class="column-2">Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, TimePicker, Span</td><td class="column-3">font-size:12;</td>
</tr>
<tr class="row-10 even">
	<td class="column-1">font-style</td><td class="column-2">Button, DatePicker, Editor, Entry, Label, Picker, SearchBar, TimePicker, Span</td><td class="column-3">font-style:bold;</td>
</tr>
<tr class="row-11 odd">
	<td class="column-1">height</td><td class="column-2">VisualElement</td><td class="column-3">height:100;</td>
</tr>
<tr class="row-12 even">
	<td class="column-1">margin<br>
margin-left<br>
margin-right<br>
margin-top<br>
margin-bottom</td><td class="column-2">View</td><td class="column-3">margin:3; (see thickness section below)</td>
</tr>
<tr class="row-13 odd">
	<td class="column-1">min-height</td><td class="column-2">VisualElement</td><td class="column-3">min-height:100;</td>
</tr>
<tr class="row-14 even">
	<td class="column-1">min-width</td><td class="column-2">VisualElement</td><td class="column-3">min-width:100;</td>
</tr>
<tr class="row-15 odd">
	<td class="column-1">opacity</td><td class="column-2">VisualElement</td><td class="column-3">opacity:0.5;</td>
</tr>
<tr class="row-16 even">
	<td class="column-1">padding<br>
padding-left<br>
padding-right<br>
padding-top<br>
padding-bottom</td><td class="column-2">Layout, Page</td><td class="column-3">padding:0; (see thickness section below)</td>
</tr>
<tr class="row-17 odd">
	<td class="column-1">text-align</td><td class="column-2">Entry, EntryCell, Label, SearchBar</td><td class="column-3">text-align:right;</td>
</tr>
<tr class="row-18 even">
	<td class="column-1">visibility</td><td class="column-2">VisualElement</td><td class="column-3">visibility:hidden;</td>
</tr>
<tr class="row-19 odd">
	<td class="column-1">width</td><td class="column-2">VisualElement</td><td class="column-3">min-width:100;</td>
</tr>
</tbody>
</table><!--kg-card-end: html--><p>Las siguientes propiedades no son soportadas:</p><!--kg-card-begin: markdown--><pre><code>all: initial.
</code></pre>
<!--kg-card-end: markdown--><p>Para los Layout (box o grid).</p><p>Propiedades abreviadas, c&#xF3;mo font y border.</p><h2 id="thickness-o-el-espesor-para-los-margenes-">Thickness (o el espesor para los margenes)</h2><p>Esto es parecido a Xamarin.Forms</p><p>Un &#xFA;nico valor, ser&#xED;a un espesor uniforme (por ejemplo, 2)</p><p>Dos valores, indican vertical y horizontal (por ejemplo, 2, 4)</p><p>Tres valores, indican el espesor superior, horizontal (izquierda y derecha), inferior (por ejemplo, 3, 4, 5)</p><p>Cuatro valores delimitados por comas, indica todos los lados. Sin embargo, los 4 valores est&#xE1;n en una secuencia diferente. Arriba, derecha, abajo, izquierda. (por ejemplo, 2, 3, 4, 2) - TRBL en lugar de LTRB.</p><h2 id="el-nombre-de-los-tama-os-">El nombre de los tama&#xF1;os.</h2><p>Estos nombres de los tama&#xF1;os no son sensibles a may&#xFA;sculas y min&#xFA;sculas:</p><!--kg-card-begin: markdown--><pre><code>default
micro
small
medium
large
</code></pre>
<!--kg-card-end: markdown--><p>El valor exacto de cada valor con nombre depende de la plataforma y depende de la vista.</p><p>Para encontrar m&#xE1;s definiciones como las que te he comentado puedes revisar la documentaci&#xF3;n oficial por parte de los chicos de Xamarin, aqu&#xED; tambi&#xE9;n vas a encontrar ejemplos y muchas m&#xE1;s novedades:</p><blockquote><a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/styles/css/?ref=wilsonvargas.com">Styling Xamarin.Forms apps using Cascading Style Sheets (CSS)</a></blockquote><h1 id="manos-al-c-digo">Manos al c&#xF3;digo</h1><p>Bueno, como es de costumbre yo he hecho un ejemplo para poder demostrar como funciona esta nueva caracter&#xED;stica de Xamarin.Forms 3.0. Vamos a realizar una peque&#xF1;a calculadora y le asignaremos los estilos desde un archivo CSS.</p><p>Lo primero que haremos ser&#xE1; crear un proyecto, vamos a la opci&#xF3;n <em>Archivo &gt; Nuevo &gt; Proyecto</em> y elegimos la opci&#xF3;n Xamarin.Forms usando .NetStandard</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/1.png" class="kg-image" alt="Usando estilos CSS Xamarin.Forms" loading="lazy"></figure><p>Ahora, para tener m&#xE1;s ordenado nuestro proyecto borraremos el archivo <em>MainPage.xaml</em> y crearemos las siguientes carpetas:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/2.PNG" class="kg-image" alt="Usando estilos CSS Xamarin.Forms" loading="lazy"></figure><p>En la carpeta <strong>Assets </strong>ser&#xE1; donde podremos nuestros archivos CSS, las dem&#xE1;s carpetas ya sabes para que las usamos.</p><p>B&#xE1;sicamente lo que explicar&#xE9; hoy ser&#xE1; usar archivos CSS en nuestros proyectos Xamarin.Forms as&#xED; que la l&#xF3;gica de la aplicaci&#xF3;n quedar&#xE1; en un segundo plano. Sin embargo, si t&#xFA; quieres saber la l&#xF3;gica de la aplicaci&#xF3;n puede encontrar este ejemplo en mi cuenta de <a href="https://github.com/wilsonvargas/DemoXamarinCSS?ref=wilsonvargas.com">GitHub</a>.</p><p>Bueno, vamos a crear nuestro archivo CSS para eso vamos hacer clic derecho sobre la carpeta <strong>Assets</strong>, luego en agregar nuevo elemento y finalmente crear un archivo CSS.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/3.png" class="kg-image" alt="Usando estilos CSS Xamarin.Forms" loading="lazy"></figure><p>Ahora vamos a escribir unos cuantos estilos para que nuestra aplicaci&#xF3;n pueda usarlos desde XAML:</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/ac181ee0dfa263e202a860f4d6eee519.js"></script><!--kg-card-end: html--><p>Hacer referencia a estos estilos desde nuestro archivo XAML es f&#xE1;cil, solo tenemos que declarar este archivo en los recursos del ContentPage utilizado:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/4.PNG" class="kg-image" alt="Usando estilos CSS Xamarin.Forms" loading="lazy"></figure><p>Como ves podemos hacer referencia de nuestros estilos solo d&#xE1;ndole un nombre a nuestro control, en este caso tenemos un Label que se llama &#x201C;result&#x201D; el cual tomar&#xE1; autom&#xE1;ticamente el estilo que hace referencia a &#xE9;l.</p><!--kg-card-begin: markdown--><pre><code>&lt;StackLayout HorizontalOptions=&quot;EndAndExpand&quot;&gt;
     &lt;Label Text=&quot;{Binding DisplayOperation}&quot; /&gt;
     &lt;Label x:Name=&quot;result&quot; Text=&quot;{Binding Result}&quot; /&gt;
&lt;/StackLayout&gt;
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/5.PNG" class="kg-image" alt="Usando estilos CSS Xamarin.Forms" loading="lazy"></figure><p>Otra forma de aplicar los estilos CSS es asignar la propiedad <strong>StyleClass </strong>y poner el nombre de nuestro estilo, para este ejemplo hacemos referencia al estilo <strong>isClear </strong>desde nuestro control tipo <strong>Button</strong></p><!--kg-card-begin: markdown--><pre><code>&lt;Button Grid.Row=&quot;0&quot; CommandParameter=&quot;C&quot; StyleClass=&quot;isClear&quot; Grid.ColumnSpan=&quot;3&quot; Text=&quot;C&quot; Command=&quot;{Binding ClearCommand}&quot; /&gt;
</code></pre>
<!--kg-card-end: markdown--><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/6.PNG" class="kg-image" alt="Usando estilos CSS Xamarin.Forms" loading="lazy"></figure><p>Adem&#xE1;s de esas dos formas est&#xE1;n las m&#xE1;s generales que simplemente es poner el nombre del control y autom&#xE1;ticamente tomar&#xE1; el estilo al momento de ser creado, por ejemplo, este estilo se asignar&#xE1; a todos los <strong>StackPanel </strong>donde se haga referencia de este archivo.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/7.PNG" class="kg-image" alt="Usando estilos CSS Xamarin.Forms" loading="lazy"></figure><p>Adem&#xE1;s, podemos aplicar otros estilos a los controles que sean hijos de otros, en el caso de este estilo se aplicar&#xE1; a los Labels que est&#xE9;n dentro de un StackPanel y tomar&#xE1; todas las propiedades declaradas.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/8.PNG" class="kg-image" alt="Usando estilos CSS Xamarin.Forms" loading="lazy"></figure><h2 id="conclusi-n">Conclusi&#xF3;n</h2><p>Como pudiste ver, hoy Xamarin Forms tiene una forma m&#xE1;s flexible de asignarle estilos a nuestras aplicaciones logrando integrar archivos CSS para este fin. En este tutorial peque&#xF1;o expliqu&#xE9; como se utiliza esta nueva caracter&#xED;stica de Xamarin Forms en su versi&#xF3;n 3.0 y cuales son las limitaciones de momento. Espero que te haya gustado, no te olvides de revisar el c&#xF3;digo del ejemplo aqu&#xED;. Nos leemos pronto &#x1F60A;</p>]]></content:encoded></item><item><title><![CDATA[Integrando Bot Framework con Xamarin.Forms Parte 2]]></title><description><![CDATA[Tutorial sencillo que explica como integrar Bot Framework con Xamarin.Forms, en este ejemplo usamos LUIS (Language Understanding Intelligent Service)]]></description><link>https://www.wilsonvargas.com/integrando-bot-framework-con-xamarin-forms-parte-2/</link><guid isPermaLink="false">6416018a25be7bc132b5eb71</guid><category><![CDATA[Xamarin]]></category><category><![CDATA[mobile]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Wed, 11 Apr 2018 01:00:00 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2019/05/image.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2019/05/image.png" alt="Integrando Bot Framework con Xamarin.Forms Parte 2"><p>Hace unas semanas escrib&#xED; la primera parte de este tutorial en el que te explico como crear, publicar y configurar un Bot para un restaurante ficticio que ten&#xED;a traducci&#xF3;n autom&#xE1;tica y se integraba con LUIS, todo esto usando Bot Framework.</p><p>Si no le&#xED;ste el primer tutorial aqu&#xED; te dejo su enlace:</p><blockquote><a href="https://wilsonvargas.com/integrando-bot-framework-con-xamarin-forms-parte-1?ref=wilsonvargas.com">Integrando Bot Framework con Xamarin.Forms - Parte 1</a></blockquote><p>Hoy, voy a mostrarte como finalmente podemos integrar este Bot que est&#xE1; publicado en Azure con una aplicaci&#xF3;n Xamarin.Forms, como les adelant&#xE9; vamos a integrar Bot usando Direct Line como canal de conexi&#xF3;n.</p><p>Para facilitarnos algunas cosas, vamos a usar algunos complementos para Xamarin.Forms que har&#xE1;n m&#xE1;s productiva nuestra codificaci&#xF3;n, los complementos que uso para este tutorial son:</p><!--kg-card-begin: markdown--><ul>
<li><a href="https://www.nuget.org/packages/Newtonsoft.Json/?ref=wilsonvargas.com">Newtonsoft.Json</a> (Para la serializaci&#xF3;n)</li>
<li><a href="https://www.nuget.org/packages/Plugins.Forms.ButtonCircle/?ref=wilsonvargas.com">Plugins.Forms.ButtonCircle</a> (Para mostrar botones circulares con iconos)</li>
<li><a href="https://www.nuget.org/packages/Xam.Plugin.Connectivity?ref=wilsonvargas.com">Xam.Plugin.Connectivity</a> (Para verificar la conectividad a internet)</li>
<li><a href="https://www.nuget.org/packages/Xam.Plugins.Settings/?ref=wilsonvargas.com">Xam.Plugin.Settings</a> (Para guardar la sesi&#xF3;n)</li>
<li><a href="https://www.nuget.org/packages/Com.Airbnb.Xamarin.Forms.Lottie/?ref=wilsonvargas.com">Com.Airbnb.Xamarin.Forms.Lottie</a> (Para la animaci&#xF3;n de carga)</li>
</ul>
<!--kg-card-end: markdown--><p>Como lo dije desde un principio, el objetivo de este tutorial es realizar la integraci&#xF3;n de nuestro Bot ya publicado, con Xamarin.Forms, as&#xED; que no entrar&#xE9; en demasiados detalles de la estructura de la aplicaci&#xF3;n, me centrar&#xE9; en las partes m&#xE1;s importantes. Si t&#xFA; no te sientes familiarizado con algo de lo que vas a ver en este tutorial puedes revisar el proyecto en mi repositorio de <a href="https://github.com/wilsonvargas/XamarinChatBot?ref=wilsonvargas.com">GitHub </a>o escribirme un tweet directamente, yo estar&#xE9; muy contento de responder tus dudas.</p><h2 id="empecemos">Empecemos</h2><p>Lo primero que haremos ser&#xE1; crear el servicio que se conectar&#xE1; al canal de nuestro Bot para mantener algunos conceptos b&#xE1;sicos de programaci&#xF3;n vamos a crear una interface y la vamos a implementar en una clase llamada <strong>BotService.cs</strong>.</p><p>En esta interface se llamar&#xE1; <strong>IBotService.cs</strong> y vamos a declarar dos m&#xE9;todos en ella:</p><!--kg-card-begin: markdown--><pre><code>Task&lt;Activity&gt; Connect();
Task&lt;Activity&gt; SendMessage(Activity message);
</code></pre>
<!--kg-card-end: markdown--><blockquote>La clase Activity nos ayudar&#xE1; a serializar los datos recibidos de nuestro canal, si deseas saber cu&#xE1;l es la estructura de esta clase puedes revisar este <a href="https://docs.microsoft.com/en-us/azure/bot-service/dotnet/bot-builder-dotnet-activities?view=azure-bot-service-3.0&amp;ref=wilsonvargas.com">enlace</a>.</blockquote><p>Ahora, la implementaci&#xF3;n de estos dos m&#xE9;todos se realiza en la clase <strong>BotService.cs</strong> el primer m&#xE9;todo que vamos a implementar es <em>Connect();</em></p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/1f8bef34a1dbddce6da4a53d704dc7bf.js"></script><!--kg-card-end: html--><p>Este m&#xE9;todo como su nombre lo dice nos ayudar&#xE1; a conectarnos a nuestro Bot mediante el canal configurado, adem&#xE1;s, uno de sus objetivos principales es el obtener el primer mensaje que emite nuestro Bot.</p><p>Ahora vamos a implementar el m&#xE9;todo <em>SendMessage(ActivityMessage message);</em></p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/270dcfb1ac62b9c7c405a45b6032a7ad.js"></script><!--kg-card-end: html--><p>Este m&#xE9;todo es el que enviar&#xE1; un mensaje a nuestro Bot y que autom&#xE1;ticamente &#xE9;l responder&#xE1; con un mensaje relacionado a lo enviado.</p><h2 id="configuraciones-adicionales">Configuraciones adicionales</h2><p>Para una mejor experiencia de usuario vamos a usar Lottie para animaci&#xF3;n que hace aparentar que nos est&#xE1; escribiendo, para hacer esta animaci&#xF3;n necesitamos un archivo tipo Json que es generado despu&#xE9;s de terminar la animaci&#xF3;n en After Effects.</p><blockquote>Si quieres saber m&#xE1;s acerca de Lottie puedes revisar la documentaci&#xF3;n oficial <a href="https://airbnb.design/lottie/?ref=wilsonvargas.com">aqu&#xED;</a>.</blockquote><p>Nosotros tenemos un control llamado <strong>AnimationView </strong>en nuestro archivo <strong>ChatView.xaml</strong></p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/7bae706808c0819dc8ee46ea2b7102a9.js"></script><!--kg-card-end: html--><p>Como ver&#xE1;s el valor del atributo Animation hace referencia al archivo &#x201C;loading.json&#x201D; este archivo tiene diferente ubicaci&#xF3;n dependiendo del proyecto final.</p><h3 id="android">Android</h3><p>En nuestro proyecto Android lo tenemos que ubicar en la carpeta <strong>Assets</strong></p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/parte2image4.png" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 2" loading="lazy"></figure><h3 id="ios">iOS</h3><p>En nuestro proyecto iOs lo tenemos que ubicar en la carpeta <strong>Resources</strong></p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/parte2image5.png" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 2" loading="lazy"></figure><h2 id="ejecutando-nuestra-aplicaci-n">Ejecutando nuestra aplicaci&#xF3;n</h2><p>Finalmente, el resultado de nuestra aplicaci&#xF3;n es el siguiente:</p><h3 id="android-1">Android</h3><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/android_portrait.png" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 2" loading="lazy"></figure><h3 id="ios-1">iOS</h3><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/ios_portrait.png" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 2" loading="lazy"></figure><p>Como ves nuestro Bot responde igual como en la primera parte, traduce su respuesta autom&#xE1;ticamente y da respuestas con sentido gracias a LUIS.</p><h2 id="conclusi-n">Conclusi&#xF3;n</h2><p>En esta segunda parte hemos integrado nuestro Bot publicado en la primera parte con Xamarin.Forms, nos hemos centrado en las partes m&#xE1;s importantes de la aplicaci&#xF3;n, as&#xED; que si t&#xFA; tienes alguna pregunta no dudes en contactarme por twitter o cualquier otro medio, adem&#xE1;s, este proyecto est&#xE1; en mi repositorio de <a href="https://github.com/wilsonvargas/XamarinChatBot?ref=wilsonvargas.com">GitHub </a>puedes revisar y modificar el c&#xF3;digo fuente a tu manera. Nos leemos pronto &#x1F601;</p>]]></content:encoded></item><item><title><![CDATA[Integrando Bot Framework con Xamarin.Forms Parte 1]]></title><description><![CDATA[Tutorial sencillo que explica como integrar Bot Framework con Xamarin.Forms, en este ejemplo usamos LUIS (Language Understanding Intelligent Service)]]></description><link>https://www.wilsonvargas.com/integrando-bot-framework-con-xamarin-forms-parte-1/</link><guid isPermaLink="false">6416018a25be7bc132b5eb72</guid><category><![CDATA[Xamarin]]></category><category><![CDATA[mobile]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Sun, 11 Mar 2018 02:02:00 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2019/05/18-2.PNG" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2019/05/18-2.PNG" alt="Integrando Bot Framework con Xamarin.Forms Parte 1"><p>Hace poco en uno de mis tweets dije que estaba creando un Bot para ayudar a los programadores en dudas o errores que se le presentaban en el camino. El proyecto se est&#xE1; avanzando pueden seguirlo es de c&#xF3;digo abierto y est&#xE1; en <a href="https://github.com/devg/JOEF2501?ref=wilsonvargas.com">Github</a>.</p><p>El d&#xED;a de hoy voy a mostrarles c&#xF3;mo crear un Bot e integrarlo con Xamarin.Forms, de hecho, el objetivo m&#xE1;s grande de este tutorial es la integraci&#xF3;n con tu aplicaci&#xF3;n m&#xF3;vil.</p><p>Lo m&#xE1;s f&#xE1;cil ser&#xED;a agregar un control WebView y poner un iframe donde llame al servicio del Bot, pero lo m&#xE1;s f&#xE1;cil no siempre es lo m&#xE1;s recomendable. La forma de integrar nuestro servicio ser&#xE1; mediante <a href="https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-concepts?view=azure-bot-service-4.0&amp;ref=wilsonvargas.com">DirectLine</a> y un objeto HttpClient para administrar la comunicaci&#xF3;n con nuestro Bot en Azure.</p><p>En esta primera parte vamos a ver la creaci&#xF3;n de nuestro Bot en Visual Studio, no quer&#xED;a hacer el t&#xED;pico &#x201C;Hola Mundo&#x201D; con este Bot as&#xED; que lo he integrado para que hable varios idiomas y adem&#xE1;s tenga caracter&#xED;sticas de poder &#x201C;entender&#x201D; lo que dicen y responder correctamente al integrarlo con <a href="https://azure.microsoft.com/es-es/services/cognitive-services/language-understanding-intelligent-service/?ref=wilsonvargas.com">LUIS</a>, como dije en un principio el objetivo de este tutorial no es explicar c&#xF3;mo funcionan los Bots o como integrarlos con LUIS o hacer que hable varios idiomas. Tal vez esto puede ser material de un pr&#xF3;ximo tutorial, pero si t&#xFA; quieres aprender m&#xE1;s acerca de estos temas puedes ver la documentaci&#xF3;n oficial:</p><p><a href="https://docs.microsoft.com/es-es/azure/bot-service/dotnet/bot-builder-dotnet-luis-dialogs?view=azure-bot-service-3.0&amp;ref=wilsonvargas.com">Usando LUIS con plantillas para C#</a></p><p><a href="https://docs.microsoft.com/es-es/azure/cognitive-services/translator/?ref=wilsonvargas.com">Gu&#xED;a r&#xE1;pida para Microsoft Translator Text API con C#</a></p><h2 id="creando-nuestros-recursos-en-azure">Creando nuestros recursos en Azure</h2><h3 id="creando-nuestro-bot">Creando nuestro Bot</h3><p>Lo primero que vamos hacer es crear nuestro Bot en el portal de Azure, para eso vamos a la opci&#xF3;n <strong>Crear un recurso</strong>, luego seleccionamos <strong>AI + Cognitive Service</strong> y finalmente elegimos la opci&#xF3;n <strong>Web App Bot</strong>.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/1.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Aqu&#xED; llenamos el nombre de nuestro Bot, elegimos nuestra suscripci&#xF3;n, elegimos el grupo de recurso y seleccionamos <strong>Crear</strong></p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/2-1.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Lo siguiente, no es lo m&#xE1;s recomendable pero como les dije este tutorial se enfoca en c&#xF3;mo integrar el Bot creado en Azure con una aplicaci&#xF3;n Xamarin.Forms, entonces lo siguiente es descargar el c&#xF3;digo fuente de nuestro Bot reci&#xE9;n creado, para eso dentro de nuestro Bot en Azure vamos a la opci&#xF3;n <strong>Build</strong> y luego <strong>Descargar archivo zip</strong> y lo guardaremos para el siguiente paso.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/3.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><h3 id="creando-nuestra-api-de-traducci-n">Creando nuestra API de traducci&#xF3;n</h3><p>Como dije l&#xED;neas arriba ser&#xE1; un Bot que hable varios idiomas, es por eso que vamos a necesitar una API que haga esa funci&#xF3;n, maravillosamente Microsoft ha creado todos esos servicios para poder usarlo en nuestras aplicaciones y as&#xED; darles un valor agregado.</p><p>Para crear nuestra API de traducci&#xF3;n vamos a la opci&#xF3;n <strong>Crear un recurso</strong>, vamos a la opci&#xF3;n buscar y copiamos &#x201C;Translator Text API&#x201D; para poder acceder a este recurso. Y luego en <strong>Crear</strong></p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/4-1.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/5-1.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Ahora simplemente ponle un nombre con sentido, elige tu suscripci&#xF3;n, elige el plan que deseas y elige el grupo de recurso o crea uno nuevo.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/6-1.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Ahora para culminar con el portal de Azure ingresamos a nuestro nuevo recurso creado, luego a la opci&#xF3;n <strong>Claves</strong> y copiamos la primera clave para luego utilizarla en la l&#xF3;gica de nuestro Bot.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/7-1.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><h2 id="a-codificar">A codificar</h2><p>Vamos a abrir el proyecto que hab&#xED;amos descargado del portal de Azure, la estructura del proyecto es muy similar a la de cualquier proyecto en Visual Studio.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/8-1.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Ahora, como para sentirnos poderosos vamos a ejecutar el proyecto, solo presionamos F5 y se ejecutar&#xE1; un proyecto tipo Web. Recuerda que nuestro Bot funcionar&#xE1; como una WebAPI entonces es necesario levantar un servicio web.</p><p>Para probar nuestro Bot no solo es necesario ejecutarlo en Visual Studio, sino tambi&#xE9;n probar el servicio en un emulador. Afortunadamente este emulador est&#xE1; disponible para su descarga gratuita, <a href="https://github.com/Microsoft/BotFramework-Emulator/releases?ref=wilsonvargas.com">t&#xFA; puedes descargarlo aqu&#xED;</a>.</p><p>Una vez iniciado el emulador tenemos que colocar una url valida que sea la de nuestro Bot.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/9.1-1.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Una vez tengamos la URL de nuestro Bot le colocamos el sufijo <strong>api/messages</strong> para poder acceder a la API de los mensajes. Y a continuaci&#xF3;n le damos a <strong>Conectar</strong></p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/9.2-1.png" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Esta es la plantilla b&#xE1;sica que descargamos del portal de Azure as&#xED; que por ahora nuestro Bot responde con la t&#xED;pica respuesta de &#x201C;Dijiste:&#x201D; seguido de lo que le escribimos.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/9.png" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Ahora vamos a hacer que nuestro Bot salude primero al momento de conectarnos a la conversaci&#xF3;n, para eso en nuestra clase <strong>MessageController.cs</strong> en el m&#xE9;todo <strong>HandleSystemMessage</strong> en la condici&#xF3;n donde compara:</p><!--kg-card-begin: markdown--><pre><code>if (message.Type == ActivityTypes.ConversationUpdate)
</code></pre>
<!--kg-card-end: markdown--><p>Escribimos lo siguiente:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/10.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Ahora vamos a probar lo que agregamos, presionamos F5 y vamos a nuestro emulador.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/11.png" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Vean que el primer mensaje lo manda nuestro Bot, con las l&#xED;neas de c&#xF3;digo que agregamos en el paso anterior.</p><h2 id="dando-vida-a-nuestro-bot">Dando vida a nuestro Bot</h2><p>Si eres lector de este blog desde hacer tiempo notar&#xE1;s que a m&#xED; siempre me gusta estructurar mi proyecto para llevar un mejor orden as&#xED; que vamos hacer eso.</p><p>Para empezar, vamos a eliminar la clase llamada <strong>EchoDialog.cs</strong> ubicada en la carpeta <strong>Dialogs</strong> ya que nosotros usaremos una llamada <strong>RootDialog</strong> que ser&#xE1; la que responda seg&#xFA;n el resultado de nuestro an&#xE1;lisis con LUIS.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/12.png" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Tambi&#xE9;n vamos a crear algunas carpetas y clases adicionales:</p><!--kg-card-begin: markdown--><pre><code>Extensions -&gt; StringExtension.cs
Helpers -&gt; ChatReponse.cs
Models -&gt; AddressKey.cs
Service -&gt; TranslateService.cs
</code></pre>
<!--kg-card-end: markdown--><p>La estructura de nuestro proyecto quedar&#xE1; as&#xED;:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/14.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><h3 id="bot-multilenguaje">Bot multilenguaje</h3><p>Voy hacer este post un poco resumido ya que es un poco extenso, solo explicar&#xE9; las partes m&#xE1;s importantes de nuestro Bot. Bueno, primero como habla varios idiomas.</p><p>Toda la l&#xF3;gica de esta caracter&#xED;stica est&#xE1; en la clase <strong>TranslateService.cs</strong> y su principal funci&#xF3;n es consumir los servicios de la API de traducci&#xF3;n de Azure (la cual creamos en los pasos anteriores).</p><p>Basicamente, tiene dos m&#xE9;todos principales: TranslateAsync() y <em>DeterminLanguageAsync()</em></p><p>El m&#xE9;todo de traducci&#xF3;n lo que hace es hacer un request a la API de traducci&#xF3;n de Azure enviando como par&#xE1;metros el texto, el idioma de origen y el idioma destino. Al recibir una respuesta leemos el contenido que viene en formato XML y del cual extraemos el String.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/c5c612a97ef0f08d4ebf033181b189e0.js"></script><!--kg-card-end: html--><p>El m&#xE9;todo para determinar hace pr&#xE1;cticamente lo mismo solo con la diferencia que se cambiar la URL a la que se hace la petici&#xF3;n.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/981ef4445e91702bec2162719c3831b9.js"></script><!--kg-card-end: html--><h3 id="integraci-n-con-luis">Integraci&#xF3;n con LUIS</h3><p>Bot Framework ya nos da varias facilidades para poder integrarle LUIS a nuestro Bot. Para esta integraci&#xF3;n simple vamos a crear una clase llamada <strong>RootDialog.cs</strong> que dar&#xE1; una respuesta traducida seg&#xFA;n la clasificaci&#xF3;n que haga con los intentos creados.</p><p>Yo te voy a dejar el archivo json de mi proyecto en LUIS para que tu solo lo importes y lo empieces a usar, <a href="https://github.com/wilsonvargas/XamarinChatBot/blob/master/LUIS/bot.json?ref=wilsonvargas.com">t&#xFA; puedes acceder al archivo aqu&#xED;.</a></p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/9bc9809c63affd652a8c346a4cf61c46.js"></script><!--kg-card-end: html--><p>Este es un m&#xE9;todo dentro de la clase <strong>RootDialog</strong> que seg&#xFA;n la calificaci&#xF3;n que responde LUIS nosotros podemos dar una respuesta adecuada. Es una forma sencilla de integrar LUIS a nuestro Bot, pero para empezar est&#xE1; m&#xE1;s que bien.</p><h2 id="probar-y-conversar">Probar y conversar</h2><p>Como ver&#xE1;s el idioma por defecto de nuestro Bot es ingl&#xE9;s, yo recomiendo crear el suyo en este idioma ya que es m&#xE1;s simple que el castellano.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/15.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Si te fijas nuestro Bot traduce autom&#xE1;ticamente su respuesta al idioma que le hablamos.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/16.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Tambi&#xE9;n a chino</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/17.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Y tambi&#xE9;n en idiomas combinados.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/18-1.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><h2 id="activaci-n-el-directline-de-nuestro-bot">Activaci&#xF3;n el DirectLine de nuestro Bot</h2><p>Para activar esta caracter&#xED;stica que nos permitir&#xE1; integrar nuestro Bot en nuestra aplicaci&#xF3;n en Xamarin.Forms tenemos que regresar al portal, seleccionar nuestro Bot, seleccionar la opci&#xF3;n <strong>Canales</strong> y luego la opci&#xF3;n <strong>DirecLine</strong></p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/20.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><p>Aqu&#xED; es donde estar&#xE1;n nuestras credenciales de acceso, simplemente aceptamos y listo, nuestro Bot ya est&#xE1; creado y habilitado para ser consumido desde una aplicaci&#xF3;n desarrollada con Xamarin.Forms.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/21.PNG" class="kg-image" alt="Integrando Bot Framework con Xamarin.Forms Parte 1" loading="lazy"></figure><h2 id="conclusi-n">Conclusi&#xF3;n</h2><p>C&#xF3;mo has visto en esta primera parte te muestro como crear el Bot e integrarlo con unos servicios de Azure como el de traducci&#xF3;n y el de LUIS. En la segunda parte integraremos nuestro Bot con una aplicaci&#xF3;n Xamarin.Forms, espero que les haya gustado. Recuerda que puedes encontrar el c&#xF3;digo de este post en mi <a href="https://github.com/wilsonvargas/XamarinChatBot?ref=wilsonvargas.com">cuenta de Github.</a> Nos leemos pronto. &#x1F60A;</p>]]></content:encoded></item><item><title><![CDATA[Cómo cambiar el icono de un MasterDetail Page Xamarin.Forms]]></title><description><![CDATA[Tutorial que muestra la forma de cambiar el icono de un MasterDetail Page más conocido como menú hamburguesa. Usando Xamarin.Forms]]></description><link>https://www.wilsonvargas.com/como-cambiar-el-icono-de-un-masterdetail-page-xamarin-forms/</link><guid isPermaLink="false">6416018a25be7bc132b5eb73</guid><category><![CDATA[Xamarin]]></category><category><![CDATA[mobile]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Tue, 21 Nov 2017 02:39:00 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2019/05/images.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2019/05/images.png" alt="C&#xF3;mo cambiar el icono de un MasterDetail Page Xamarin.Forms"><p>Hola de nuevo, hace unas semanas escrib&#xED; un art&#xED;culo sobre un NavigationPage personalizado, para ser m&#xE1;s espec&#xED;fico que el fondo de la barra de navegaci&#xF3;n sea gradiente. Aqu&#xED; expliqu&#xE9; como usar los Custom Renderers y definici&#xF3;n bien b&#xE1;sica de c&#xF3;mo funciona y en qu&#xE9; casos usarlos.</p><p>Si quieres leerle el art&#xED;culo anterior lo puedes hacer aqu&#xED;:</p><blockquote><a href="https://blog.wilsonvargas.com/personalizando-un-navigationbar?ref=wilsonvargas.com">Personalizando un NavigationBar en Xamarin.Forms</a></blockquote><p>En este articulo voy a mostrar c&#xF3;mo cambiar el icono de un control MasterDetail. Un control de este tipo es muy usado desde hace bastante tiempo atr&#xE1;s, es b&#xE1;sicamente un men&#xFA;, el men&#xFA; hamburguesa&#x2026;</p><p>Este men&#xFA; es muy usado en muchas aplicaciones m&#xF3;viles y su funcionamiento radica en un men&#xFA; desplegable de izquierda a derecha accionado por un bot&#xF3;n en la parte superior izquierda (algunas veces derecha) del tel&#xE9;fono.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/master.png" class="kg-image" alt="C&#xF3;mo cambiar el icono de un MasterDetail Page Xamarin.Forms" loading="lazy"></figure><p>Lo que hoy haremos es personalizar el bot&#xF3;n que muestra el men&#xFA;, la verdad no s&#xE9; si esto sea un bug en Xamarin Forms o est&#xE9; configurado as&#xED;, pero en teor&#xED;a podr&#xED;amos modificar la imagen de este bot&#xF3;n desde nuestro archivo XAML en nuestro proyecto compartido.</p><p>En fin, si t&#xFA; sabes cu&#xE1;l es la causa de este extra&#xF1;o caso no dudes en dejar un comentario para poder actualizar este art&#xED;culo.</p><h1 id="manos-al-c-digo">Manos al c&#xF3;digo</h1><p>Empezaremos creando un MasterDetailPage de la siguiente forma:</p><!--kg-card-begin: markdown--><pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?&gt;
&lt;MasterDetailPage  xmlns=&quot;http://xamarin.com/schemas/2014/forms&quot;
         xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;
         xmlns:local=&quot;clr-namespace:CustomIcon.Views;assembly=CustomIcon&quot;
         Title=&quot;MainPage&quot;
         Icon=&quot;newIcon.png&quot;
         x:Class=&quot;CustomIcon.Views.MainPage&quot;&gt;
    &lt;MasterDetailPage.Master&gt;
        &lt;local:MasterPage x:Name=&quot;masterPage&quot; /&gt;
    &lt;/MasterDetailPage.Master&gt;
    &lt;MasterDetailPage.Detail&gt;
        &lt;NavigationPage&gt;
            &lt;x:Arguments&gt;
                &lt;local:Page1 /&gt;
            &lt;/x:Arguments&gt;
        &lt;/NavigationPage&gt;
    &lt;/MasterDetailPage.Detail&gt;
&lt;/MasterDetailPage&gt;
</code></pre>
<!--kg-card-end: markdown--><p>Recuerda que este no es un tutorial de c&#xF3;mo crear un men&#xFA; MasterDetail sino de como personalizar su icono as&#xED; que voy a explicar cosas que tal vez no entiendas si est&#xE1;s iniciando con Xamarin Forms. Recuerda que puedes revisar <a href="https://github.com/wilsonvargas/CustomIconNavigationPage?ref=wilsonvargas.com">el c&#xF3;digo de este ejemplo en Github</a> si quiere saber c&#xF3;mo se ha creado este ejemplo.</p><p>Al tener creado nuestro control MasterDetail ahora podemos personalizar nuestro icono.</p><h1 id="android">Android</h1><p>En Android tienes que crear un Custom Renderer que extienda de <strong>MasterDetailPageRenderer </strong>e invocar a un <strong>ExportRenderer </strong>haciendo referencia a nuestra p&#xE1;gina principal, es decir la que tiene el control MasterDetail.</p><p>Ahora tenemos que sobre escribir el m&#xE9;todo <strong>OnLayout </strong>para poder reemplazar el icono, obtener el Toolbar y luego iterar entre todos los iconos que tenga y poner asignar la imagen que nosotros querramos.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/7ef73a98e06269afa6928a7c67992cc8.js"></script><!--kg-card-end: html--><p>Finalmente, nuestro control MasterDetail queda as&#xED;:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/android.png" class="kg-image" alt="C&#xF3;mo cambiar el icono de un MasterDetail Page Xamarin.Forms" loading="lazy"></figure><p>#iOS</p><p>En teor&#xED;a en iOS deber&#xED;a funcionar sin agregar ning&#xFA;n Custom Renderer, ya que iOS agrega la imagen que le asignamos en nuestro archivo <strong>MasterDetail.xaml</strong> pero extra&#xF1;amente el mismo sistema operativo asigna el color a la imagen.</p><p>De hecho, eso no es lo que queremos para eso tenemos que crear un Custom Renderer que extienda de <strong>PhoneMasterDetailRenderer </strong>e invocar a un <strong>ExportRenderer </strong>haciendo referencia a nuestra p&#xE1;gina principal, es decir la que tiene el control MasterDetail.</p><p>Ahora tenemos que sobre escribir el m&#xE9;todo <strong>ViewDidLayoutSubviews </strong>para poder reemplazar el icono.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/7529e243ef7c79e0a72cbf2586843a53.js"></script><!--kg-card-end: html--><p>Finalmente, nuestro control MasterDetail queda as&#xED;:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/ios-1.png" class="kg-image" alt="C&#xF3;mo cambiar el icono de un MasterDetail Page Xamarin.Forms" loading="lazy"></figure><h1 id="conclusi-n">Conclusi&#xF3;n</h1><p>Bueno, esto ha sido un art&#xED;culo super corto, pero creo que muy &#xFA;til donde mostr&#xE9; como cambiar el icono hamburguesa de un control MasterDetailPage usando los Custom Renderer. Recuerda que este ejemplo lo puedes encontrar en <a href="https://github.com/wilsonvargas/CustomIconNavigationPage?ref=wilsonvargas.com">mi repositorio de Github</a>. Nos leemos pronto, sigan xamarineando. &#x1F60E;</p>]]></content:encoded></item><item><title><![CDATA[Personalizando un NavigationBar en Xamarin.Forms]]></title><description><![CDATA[En este tutorial te explico un concepto básico de los Custom Renderers y como es la implementación en cada plataforma.]]></description><link>https://www.wilsonvargas.com/personalizando-un-navigationbar/</link><guid isPermaLink="false">6416018a25be7bc132b5eb74</guid><category><![CDATA[Xamarin]]></category><category><![CDATA[mobile]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Sun, 22 Oct 2017 03:00:00 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2019/05/sample.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2019/05/sample.png" alt="Personalizando un NavigationBar en Xamarin.Forms"><p>Esto articulo nace a partir de una <a href="https://stackoverflow.com/questions/46809733/how-to-add-a-gradient-in-xamarin-forms-toolbar-and-uinavigationbar/46816859?ref=wilsonvargas.com#46816859">pregunta en StackOverflow</a>, normalmente yo entro al finalizar mi d&#xED;a a esta p&#xE1;gina de preguntas y respuesta donde de vez en cuando doy algunas sugerencias.</p><p>Hace unos d&#xED;as surgi&#xF3; una pregunta por un usuario en este portal, b&#xE1;sicamente la ayuda que necesitaba era que quer&#xED;a saber c&#xF3;mo personalizar el NavigationBar de una aplicaci&#xF3;n Xamarin.Forms para Android y iOS. Este control tambi&#xE9;n es conocido como AppBar en Android y UINavigationBar en iOS y este usuario necesitaba que quede de esta manera:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/YuXXc.jpg" class="kg-image" alt="Personalizando un NavigationBar en Xamarin.Forms" loading="lazy"></figure><p>Lo primero que se nos debe venir a la mente cuando queremos personalizar un control en Xamarin.Forms es usar los <a href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/?ref=wilsonvargas.com">Custom Renderers</a>, esto te permite cambiar la apariencia y el comportamiento de los controles en cada plataforma.</p><p>Recuerda que las interfaces de usuario en Xamarin.Forms se representan con los controles nativos de la plataforma de destino, lo que permite que las aplicaciones de Xamarin.Forms conserven el aspecto para cada plataforma.</p><p>Los Custom Renderers permiten a los desarrolladores anular este proceso para personalizar el aspecto y el comportamiento de los controles de Xamarin.Forms en cada plataforma.</p><p>Bueno ahora que ya sabes algo b&#xE1;sico acerca de los Custom Renderers es hora de aprender c&#xF3;mo se utilizan y que fue lo que hice para solucionar el problema de este usuario.</p><h2 id="manos-a-la-obra">Manos a la obra</h2><p>Lo primero que tenemos que hacer es crear una clase para este ejemplo le pondremos <strong>NavigationPageGradientHeader</strong>, esta clase que nosotros creamos tendr&#xE1; que extender del control que queramos personalizar, en mi caso extender&#xE1; del control <strong>NavigationPage</strong>.</p><p>Ahora crearemos las <strong>BindableProperties </strong>que ser&#xE1;n las encargadas de hacer Binding a los valores que nosotros referenciemos, un BindableProperty tiene la siguiente estructura:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/image1-1.png" class="kg-image" alt="Personalizando un NavigationBar en Xamarin.Forms" loading="lazy"></figure><p><strong>propertyName</strong>: Es la propiedad a la cual har&#xE1; referencia para asignar o retornar alg&#xFA;n valor. En mi caso yo tengo dos propiedades una llamada RightColor que ser&#xE1; el color con el cual comenzar&#xE1; el gradiente y LeftRight con el cual terminar&#xE1;.</p><p><strong>returnType</strong>: Ser&#xE1; el tipo de dato que retornar&#xE1; est&#xE1; propiedad, en nuestro caso ser&#xE1; un tipo de la clase Xamarin.Forms.Color, pero si est&#xE1;s necesitas retornar cualquier otro tipo de dato puedes hacerlo.</p><p><strong>declaringType</strong>: Es la clase donde se est&#xE1; declarando esta BindableProperty en este caso ser&#xE1; la misma clase donde estamos, pero podemos declarar BindableProperties fuera de la clase y referenciarla por su nombre.</p><p><strong>defaultValue</strong>: Es el valor por defecto que tendr&#xE1; nuestra BindableProperty, como nuestro tipo de dato de retorno es de tipo Color, nuestro valor por defecto tendr&#xE1; que ser del mismo tipo de dato. &#xA0;En mi caso le puse Red.</p><p>Ahora creamos las propiedades que har&#xE1;n referencia nuestra <strong>BindableProperty </strong>ya sea para asignar u obtener el valor de esa propiedad.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/image3.PNG" class="kg-image" alt="Personalizando un NavigationBar en Xamarin.Forms" loading="lazy"></figure><p>Finalmente, nuestra clase queda de la siguiente manera:</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/36e837f000b3e2a5292665b12d2bc2d3.js"></script><!--kg-card-end: html--><h2 id="personalizando-el-control-seg-n-la-plataforma">Personalizando el control seg&#xFA;n la plataforma</h2><p>Como te coment&#xE9; l&#xED;neas arriba un Custom Renderer permite cambiar la apariencia y el comportamiento de los controles en cada plataforma, este ejemplo est&#xE1; destinado solo para Android y iOS. No lo he hecho para UWP por dos razones, dej&#xE9; de desarrollar hace mucho para UWP y no conozco mucho sus APIs y debido al primer motivo no s&#xE9; ni por d&#xF3;nde empezar para personalizar el NavigationBar en UWP.</p><p>Ustedes pueden colaborar haciendo este que control personalizado soporte UWP enviando alg&#xFA;n pull request <a href="https://github.com/wilsonvargas/GradientNavigationToolbarXamarinForms?ref=wilsonvargas.com">al repositorio de Github</a>.</p><h3 id="android">Android</h3><p>En Android es un tanto tedioso cambiar el control <strong>ActionBar</strong>, debido a que desde algunas actualizaciones la clase MainActivity extiende de <strong>FormsAppCompatActivity</strong> y esto de por s&#xED; ya tiene una propiedad llamada <strong>ToolbarResource</strong> la cual hace el t&#xED;pico <em>SetActionBar()</em> entonces crear una clase Renderer que extienda de PageRenderer ser&#xED;a in&#xFA;til ya que no ver&#xED;amos ning&#xFA;n cambio.</p><p>Para esto debemos crear un drawable llamado <strong>grandient.xml</strong> donde pondremos un shape que tenga el grandiente, este drawable tendr&#xE1; la siguiente estructura:</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/4ae098e265cf538774a018836fcb4097.js"></script><!--kg-card-end: html--><p>Ahora solo en nuestro archivo <strong>Toolbar.axml</strong> ubicado en la carpeta layout haremos referencia a este drawable, finalmente nuestro Toolbar que de la siguiente manera:</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/3a4a34089cde4cba30ceda610aa1bfdd.js"></script><!--kg-card-end: html--><p>Esto har&#xE1; que nuestro ActionBar en Android se vea de esta forma:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/android--1-.png" class="kg-image" alt="Personalizando un NavigationBar en Xamarin.Forms" loading="lazy"></figure><h3 id="ios">iOS</h3><p>En iOS es diferente y s&#xED; funciona lo de agregar un Renderer que extienda de PageRenderer en esta clase lo que haremos es crear un <strong>GradientLayer</strong> que ser&#xE1; el que dar&#xE1; el efecto de gradiente a nuestro NavigationBar.</p><p>A este GradientLayer le vamos asignar los colores que ser&#xE1;n asignados en nuestra PCL, para esto convertimos la propiedad Element en un objeto de nuestra clase principal y luego de eso solo llamamos a las propiedades correspondientes en nuestro caso RightColor y LeftColor. Recuerda que est&#xE1;s propiedades son del tipo de dato Xamarin.Forms.Color y el GradientLayer recibe el tipo CGColor, lo &#xFA;nico que tenemos que hacer es invocar al m&#xE9;todo <em>ToCGColor()</em>, as&#xED; de f&#xE1;cil podemos convertir nuestro tipo de dato de Xamarin.Forms a la plataforma nativa. Xamarin es maravilloso!</p><p>Ahora convertimos este <strong>GradientLayer </strong>a imagen y asignamos esta imagen a nuestro NavigationBar.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/6b89b58c7bc2eb9382a19dc55a8dcfb6.js"></script><!--kg-card-end: html--><p>Esto har&#xE1; que nuestro UINavigationBar se vea de esta forma:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/ios--1-.png" class="kg-image" alt="Personalizando un NavigationBar en Xamarin.Forms" loading="lazy"></figure><h3 id="proyecto-compartido">Proyecto compartido</h3><p>Debido a que este es un control que extiende de NavigationPage nosotros debemos de invocar a este control en nuestro archivo App.xaml.cs:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/screen1.PNG" class="kg-image" alt="Personalizando un NavigationBar en Xamarin.Forms" loading="lazy"></figure><h2 id="conclusi-n">Conclusi&#xF3;n</h2><p>Bueno, este es un micro post que naci&#xF3; de una pregunta en StackOverflow, te expliqu&#xE9; un concepto b&#xE1;sico de los Custom Renderers y como es la implementaci&#xF3;n en cada plataforma.</p><p>Recuerda que el c&#xF3;digo de este ejemplo est&#xE1; en <a href="https://github.com/wilsonvargas/GradientNavigationToolbarXamarinForms?ref=wilsonvargas.com">mi repositorio de Github</a>.</p><p>Adem&#xE1;s la pregunta de StackOverflow que hago referencia en este articulo la puedes encontrar aqu&#xED;:</p><p><a href="https://stackoverflow.com/questions/46809733/how-to-add-a-gradient-in-xamarin-forms-toolbar-and-uinavigationbar/46816859?ref=wilsonvargas.com#46816859" rel="nofollow">How to Add a Gradient in Xamarin forms Toolbar and UINavigationBar</a></p>]]></content:encoded></item><item><title><![CDATA[Creando botones circulares en Xamarin.Forms]]></title><description><![CDATA[Tutorial que explica de forma sencilla a cómo crear botones circulares en Xamarin.Forms, incluyen un icono, texto o imagen.]]></description><link>https://www.wilsonvargas.com/creando-botones-circulares-en-xamarin-forms/</link><guid isPermaLink="false">6416018a25be7bc132b5eb75</guid><category><![CDATA[Xamarin]]></category><category><![CDATA[mobile]]></category><dc:creator><![CDATA[Wilson Vargas]]></dc:creator><pubDate>Tue, 22 Aug 2017 02:11:00 GMT</pubDate><media:content url="https://www.wilsonvargas.com/content/images/2019/05/image-2.png" medium="image"/><content:encoded><![CDATA[<img src="https://www.wilsonvargas.com/content/images/2019/05/image-2.png" alt="Creando botones circulares en Xamarin.Forms"><p>El d&#xED;a de hoy voy a explicar de manera sencilla como crear botones circulares en Xamarin.Forms incluyendo un icono.</p><p>Yo vengo desarrollando apps desde hace mucho, con Xamarin desde hace alg&#xFA;n tiempo y he tenido algunos proyectos en los cuales los dise&#xF1;adores necesitaban este tipo de botones y yo como desarrollador ten&#xED;a que ingeni&#xE1;rmelas para lograr esos requerimientos. De hecho, muchos de ustedes son usuarios de aplicaciones m&#xF3;viles y de seguro han visto mucho de estos botones en las aplicaciones que usan.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/image-1.png" class="kg-image" alt="Creando botones circulares en Xamarin.Forms" loading="lazy"></figure><p>Vamos a utilizar un control llamado <a href="https://www.nuget.org/packages/Plugins.Forms.ButtonCircle/?ref=wilsonvargas.com">ButtonCircle </a>que de hecho fue creado por m&#xED;, como les coment&#xE9; he tenido que ingeni&#xE1;rmelas para realizar esa petici&#xF3;n de los botones circulares que ped&#xED;a el equipo de dise&#xF1;o. Es por eso que como buen desarrollador que piensa en los dem&#xE1;s y no quiere que los dem&#xE1;s sufran decid&#xED; crear este control para Xamarin.Form.</p><p>Para crear este control me bas&#xE9; en otro que tiene las mismas caracter&#xED;sticas, este control es <a href="https://github.com/jamesmontemagno/ImageCirclePlugin?ref=wilsonvargas.com">ImageCircle </a>creado por <a href="https://montemagno.com/?ref=wilsonvargas.com">James Montemagno</a>, pero aparte de la caracter&#xED;stica circular necesitaba que tenga un icono dentro del bot&#xF3;n. Es por eso que vi la forma de utilizar una fuente de iconos creada por Google y que est&#xE1; a disposici&#xF3;n de todos, y bueno b&#xE1;sicamente lo que hice fue aplicar esta fuente al texto de nuestro bot&#xF3;n.</p><p>En fin, si ustedes quieren revisar el c&#xF3;digo fuente de este control puedes revisar <a href="http://github.com/wilsonvargas?ref=wilsonvargas.com">mi perfil de Github</a> o ir directamente al <a href="https://github.com/wilsonvargas/ButtonCirclePlugin?ref=wilsonvargas.com">repositorio de este control</a>.</p><h2 id="a-codificar">A codificar</h2><p>Bueno, crear botones circulares ahora es m&#xE1;s f&#xE1;cil usando este control. Al menos ya cumpl&#xED; uno de mis primeros objetivos que era facilitarle la vida a los dem&#xE1;s.</p><p>Lo primero que vamos hacer es crear un proyecto en Visual Studio.</p><p>Como siempre, la ruta es: <em>Archivo -&gt; Nuevo -&gt; Proyecto</em> en la ventana que aparece vamos a crear un proyecto de tipo <strong>Aplicaci&#xF3;n multiplataforma</strong> en este caso le vamos a poner como nombre <em>&#x201C;CircularButtonSample&#x201D;</em>, pero en tu caso puedes ponerle el nombre que quieras o simplemente agregar a un proyecto ya existente.</p><p>En la ventana siguiente lo &#xFA;nico que tenemos que hacer es elegir si deseamos una aplicaci&#xF3;n en blanco o una de las plantillas, si deseamos Xamarin.Forms o una aplicaci&#xF3;n nativa, y si deseamos un proyecto compartido o una biblioteca de clases portable (PCL).</p><p>En este caso vamos a elegir lo que vemos en la imagen:</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/1-1.png" class="kg-image" alt="Creando botones circulares en Xamarin.Forms" loading="lazy"></figure><p>Ahora vamos a descargar e instalar el control creado, este control est&#xE1; disponible en <a href="https://www.nuget.org/packages/Plugins.Forms.ButtonCircle/?ref=wilsonvargas.com">nuget </a>para que ustedes lo puedan descargar.</p><p>Hacemos clic derecho sobre nuestra soluci&#xF3;n y luego vamos a <strong>Administrar paquetes Nuget para la soluci&#xF3;n</strong>, ahora en la opci&#xF3;n <strong>Navegar </strong>vamos a buscar el control <em>&#x201C;ButtonCircle&#x201D;.</em></p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/2-1.JPG" class="kg-image" alt="Creando botones circulares en Xamarin.Forms" loading="lazy"></figure><p>Selecciona las plataformas de destino que desees.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/3.JPG" class="kg-image" alt="Creando botones circulares en Xamarin.Forms" loading="lazy"></figure><p>Una vez instalado podemos usar este control, en nuestra biblioteca de clases portable (PCL) vamos agregar un espacio de nombre en el archivo <strong>MainPage.xaml</strong></p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/4.JPG" class="kg-image" alt="Creando botones circulares en Xamarin.Forms" loading="lazy"></figure><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/5-1.JPG" class="kg-image" alt="Creando botones circulares en Xamarin.Forms" loading="lazy"></figure><p>Una vez agregado este espacio de nombre podemos empezar a utilizar nuestro control en este archivo que representa a la vista.</p><p>Ahora usamos el alias que le hemos puesto, en este caso <em>&#x201C;circularButton&#x201D;</em> para acceder a las propiedades de nuestro control.</p><!--kg-card-begin: html--><script src="https://gist.github.com/wilsonvargas/461dd85d184b6c9188568788bf61a89b.js"></script><!--kg-card-end: html--><p>Como puedes observar nuestro control tiene una propiedad llamada <em>Icon </em>la cual es la encargada de renderizar el icono que deseamos mostrar, para esto nosotros le pasamos el nombre del icono en formato String y el control se encargar&#xE1; de renderizar la figura.</p><p>La lista de nombres de los iconos est&#xE1; disponible <a href="https://github.com/wilsonvargas/ButtonCirclePlugin/tree/master/src/ButtonCircle/ButtonCircle.FormsPlugin.Abstractions?ref=wilsonvargas.com">aqu&#xED; </a>para que puedas usar los que m&#xE1;s se apeguen a tus necesidades.</p><p>Tambi&#xE9;n puedes usar este control con la propiedad <em>Text </em>por si quieres solo mostrar texto en un bot&#xF3;n circular.</p><p>Esta caracter&#xED;stica la agreg&#xF3; <a href="https://github.com/agrigg?ref=wilsonvargas.com">Austin Grigg</a> en el siguiente <a href="https://github.com/wilsonvargas/ButtonCirclePlugin/pull/2?ref=wilsonvargas.com">pull request</a>, pero con una peque&#xF1;a falla que no mostraba los iconos cuando no asign&#xE1;bamos la propiedad Text. He solucionado ese problema agregando a la lista de iconos uno con el nombre <em>ic_default </em>que es icono en blanco y que la verificar el icono renderiza el texto como se debe hacer.</p><p>Es por eso que como ves en el ejemplo se puede obviar la propiedad Icon y solo usar la propiedad Text y viceversa, en ambos casos funciona bien.</p><h2 id="configuraci-n-de-android">Configuraci&#xF3;n de Android</h2><p>Para poder renderizar este control en Android es necesario llamar al m&#xE9;todo <strong>Init()</strong> dentro de la clase <em>MainActivity </em>en el m&#xE9;todo <em>OnCreate </em>antes de llamar al m&#xE9;todo Init() de Xamarin.Forms.</p><!--kg-card-begin: markdown--><pre><code>protected override void OnCreate(Bundle bundle)
{
    TabLayoutResource = Resource.Layout.Tabbar;
    ToolbarResource = Resource.Layout.Toolbar;

    base.OnCreate(bundle);

    ButtonCircle.FormsPlugin.iOS.ButtonCircleRenderer.Init();
    
    global::Xamarin.Forms.Forms.Init(this, bundle);
    LoadApplication(new App());
}
</code></pre>
<!--kg-card-end: markdown--><h2 id="configuraci-n-en-ios">Configuraci&#xF3;n en iOS</h2><p>Al igual que en Android nosotros tenemos que llamar al m&#xE9;todo <strong>Init()</strong> dentro de la clase <strong><em>AppDelegate </em></strong>en el m&#xE9;todo <em>FinishedLaunching </em>antes de llamar al m&#xE9;todo <em>Init()</em> de Xamarin.Forms</p><!--kg-card-begin: markdown--><pre><code>public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init();
    LoadApplication(new App());
    ButtonCircle.FormsPlugin.iOS.ButtonCircleRenderer.Init();
    return base.FinishedLaunching(app, options);
}
</code></pre>
<!--kg-card-end: markdown--><p>Adem&#xE1;s de hacer esto en iOS tenemos que hacer algo adicional, lo que tenemos que hacer es declarar la fuente que vamos usar para los iconos, si bien es cierto el formato .ttf de la fuente viene incluido en este Plugin en iOS es necesario declararlo agregando una llave en el archivo <strong>Info.plist</strong> de la siguiente manera:</p><!--kg-card-begin: markdown--><pre><code>&lt;array&gt;
  &lt;string&gt;materialicons.ttf&lt;/string&gt;
  &lt;string&gt;fontawesome.ttf&lt;/string&gt;
&lt;/array&gt;
</code></pre>
<!--kg-card-end: markdown--><h2 id="demostraci-n">Demostraci&#xF3;n</h2><h3 id="android">Android</h3><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/android-1.png" class="kg-image" alt="Creando botones circulares en Xamarin.Forms" loading="lazy"></figure><h3 id="ios">iOS</h3><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/ios-2.png" class="kg-image" alt="Creando botones circulares en Xamarin.Forms" loading="lazy"></figure><h3 id="windows">Windows</h3><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/uwp.png" class="kg-image" alt="Creando botones circulares en Xamarin.Forms" loading="lazy"></figure><h3 id="algunos-problemas">Algunos problemas</h3><p>En Android 6.0 hay un problema de renderizado, esto lo report&#xF3; <a href="https://github.com/NGame1?ref=wilsonvargas.com">NGame1 </a>hace unas semanas. La soluci&#xF3;n para esto es agregar la propiedad <em>&#x201C;BorderRadius&#x201D; </em>y asignar el mismo valor que la altura del bot&#xF3;n. &#xA0;Algo parecido a esto:</p><!--kg-card-begin: markdown--><pre><code>&lt;local:CircleButton Icon=&quot;ic_directions_bike&quot; 
                    FontSize=&quot;30&quot; TextColor=&quot;Black&quot;
                    HeightRequest=&quot;70&quot; WidthRequest=&quot;70&quot; 
                    BorderRadius=&quot;70&quot; BackgroundColor=&quot;#DCDCDC&quot;/&gt;
&lt;!--This button is not exactly circular--&gt;
&lt;local:CircleButton FontSize=&quot;30&quot; TextColor=&quot;Black&quot; 
                    HeightRequest=&quot;70&quot; WidthRequest=&quot;200&quot; 
                    BorderRadius=&quot;20&quot; BackgroundColor=&quot;#DCDCDC&quot;/&gt;
</code></pre>
<!--kg-card-end: markdown--><p>Esta propiedad permite botones que no sean exactamente circulares como se ve en esta imagen.</p><figure class="kg-card kg-image-card"><img src="https://www.wilsonvargas.com/content/images/2019/05/demo.png" class="kg-image" alt="Creando botones circulares en Xamarin.Forms" loading="lazy"></figure><h2 id="conclusi-n">Conclusi&#xF3;n</h2><p>Bueno, el tutorial de hoy fue algo peque&#xF1;o. B&#xE1;sicamente les mostr&#xE9; como usar este plugin que cre&#xE9; hace poco, voy a estar muy contento si es que ustedes le hacen alguna mejora a este plugin, me gustar&#xED;a agregar m&#xE1;s tipos de iconos, poner iconos y textos a la vez y varias cosas m&#xE1;s. Esperar&#xE9; muy contento sus pull request. Al igual si tienen alg&#xFA;n problema con el plugin rep&#xF3;rtenlo en el<a href="https://github.com/wilsonvargas/ButtonCirclePlugin/?ref=wilsonvargas.com"> repositorio de Github</a> estar&#xE9; encantando de darle una soluci&#xF3;n. Nos leemos pronto &#x2764;</p>]]></content:encoded></item></channel></rss>