<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>HangZone</title>
	<atom:link href="https://hangzone.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://hangzone.com/</link>
	<description>Mobile App and Game Development</description>
	<lastBuildDate>Tue, 31 Mar 2020 14:49:42 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://hangzone.com/wp-content/uploads/2022/04/cropped-HZ-Logo-32x32.png</url>
	<title>HangZone</title>
	<link>https://hangzone.com/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>How to Make a Location Check-In App in Xamarin.Forms Part One</title>
		<link>https://hangzone.com/how-to-make-a-location-check-in-app-in-xamarin-forms-part-one/</link>
		
		<dc:creator><![CDATA[Tyler Bandy]]></dc:creator>
		<pubDate>Tue, 31 Mar 2020 14:49:42 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=2093</guid>

					<description><![CDATA[<p>It’s been about ten years since Foursquare launched their mobile check-in app. In case you missed out on that trend, the original Foursquare app was all about checking into locations ...</p>
<p>The post <a href="https://hangzone.com/how-to-make-a-location-check-in-app-in-xamarin-forms-part-one/">How to Make a Location Check-In App in Xamarin.Forms Part One</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>It’s been about ten years since Foursquare launched their mobile check-in app. In case you missed out on that trend, the original Foursquare app was all about checking into locations to earn badges. Whoever logged the most activity at a location over a trailing month earned the title of mayor. Sometimes restaurants gave special offers to the mayor, like free guacamole on a burrito, so this was serious business!</p>
<p>These were the early days of gamification of mundane activities, so there was a pretty low bar to get people excited. Eventually, people got tired of having to check-in everywhere, and the app lost popularity.</p>
<h2>Let’s Bring it Back</h2>
<p>They say fads come in cycles though. Maybe it’s time to bring the check-in trend back to the masses. Let’s start with your immediate group of friends anyway. We’ll pick one place you guys all like to go. Everyone will have the ability to check-in once every 12 hours. Whoever has the most check-ins is the mayor. We’ll build the app with Xamarin.Forms. This will be a two part series. We’ll handle the location based functionality in part one. We’ll take care of the server functionality in part 2.</p>
<h2>Start a Xamarin Forms Project</h2>
<p>Fire up Visual Studio. I’m using a Windows computer, but feel free to use a Mac. Some of the menus will be a bit different though. Create a new project. Select a Xamarin.Forms template. I’m going to call this project &#8220;Mayor&#8221;. Go with the blank template option, and select Android and iOS for the platforms.</p>
<h2>Set-up Location Libraries and Permissions</h2>
<p>For starters, the Xamarin Essentials NuGet package houses all of the location functionality that we need. Fortunately, it’s already added to our project by default, along with the necessary start-up code.</p>
<p>That just leaves us with permissions. We’ll start with Android. Open up your AndroidManifest.xml file. It’s in the Properties folder in case you can’t find it. Inside the manifest node, add the following code.</p>
<pre><code>&lt;uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /&gt;
&lt;uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /&gt;
&lt;uses-feature android:name="android.hardware.location" android:required="false" /&gt;
&lt;uses-feature android:name="android.hardware.location.gps" android:required="false" /&gt;
&lt;uses-feature android:name="android.hardware.location.network" android:required="false" /&gt;
</code></pre>
<p>Now let’s do iOS. Right click your Info.plist and select View Code. Inside the dictionary, add the following key value pair.</p>
<pre><code>&lt;key&gt;NSLocationWhenInUseUsageDescription&lt;/key&gt;
&lt;string&gt;We need to verify your location when you check-in.&lt;/string&gt;
</code></pre>
<p>This will show the user a prompt for why we’re requesting to track their location.</p>
<h2>The Interface</h2>
<p>Let’s put together a basic interface now. We want a label that shows how many times you’ve checked-in during the past month. We also want a button to check-in. Finally, we want a label that displays a confirmation or error code for your check-in attempt.</p>
<p>Open up MainPage.xaml and remove the default label inside of the StackLayout. Let’s put our own interface elements inside of the StackLayout.</p>
<pre><code>&lt;Label
    x:Name="MessageLabel"
    HorizontalOptions="Center"
    HorizontalTextAlignment="Center"
    Text="Remember to Check-In!"
    VerticalOptions="EndAndExpand" /&gt;
&lt;Button
    x:Name="CheckInButton"
    Margin="10"
    HorizontalOptions="Center"
    Text="Check-In"
    VerticalOptions="CenterAndExpand" /&gt;
&lt;Label
    x:Name="CountLabel"
    HorizontalOptions="Center"
    HorizontalTextAlignment="Center"
    Text="Check-In Count: 0"
    VerticalOptions="StartAndExpand /&gt;
</code></pre>
<p>It’s a pretty simple layout, but it will do. The top label has a generic instructional message as a filler for now. When the user attempts to login, we’ll replace the text in this label with a success or error message. The label at the bottom will show the total number of check-ins. Go ahead and give the code a run. Your screen should look like this.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Layout.png"><img decoding="async" class="alignnone size-medium wp-image-2100" src="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Layout-139x300.png" alt="Check In Layout" width="139" height="300" srcset="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Layout-139x300.png 139w, https://hangzone.com/wp-content/uploads/2020/03/Check-In-Layout-473x1024.png 473w, https://hangzone.com/wp-content/uploads/2020/03/Check-In-Layout.png 1125w" sizes="(max-width: 139px) 100vw, 139px" /></a></p>
<h2>The Check-In Code</h2>
<p>Normally, I would put the check-in code in a ViewModel and set-up binding with our labels and button. In the interest of keeping this demo simple, let’s just do our work in the code-behind. Open MainPage.xaml.cs. At the top of the page, add a line to access the Xamarin Essentials library.</p>
<pre><code>Using Xamarin.Essenetials;
</code></pre>
<p>Now, below the MainPage constructor, add a new function.</p>
<pre><code>private async Task CheckIn()
{
    try
    {
        var request = new GeolocationRequest(GeolocationAccuracy.Medium);
        var userLocation = await Geolocation.GetLocationAsync(request);

        if (userLocation != null)
        {
            try
            {
                var locations = await Geocoding.GetLocationsAsync("3330 Cumberland Blvd Suite 500, Atlanta, GA 30339");

                var clientLocation = locations?.FirstOrDefault();
                if (clientLocation != null)
                {
                    double kilometers = Location.CalculateDistance(userLocation, clientLocation, DistanceUnits.Kilometers);
                    if (kilometers &lt; 0.6) { // the accuracy should be good within 500 meters await Device.InvokeOnMainThreadAsync(() =&gt; MessageLabel.Text = "You successfully checked-in!");
                    }
                    else
                    {
                        await Device.InvokeOnMainThreadAsync(() =&gt; MessageLabel.Text = "You need to be closer to check-in.");
                    }
                }
            }
            catch (FeatureNotSupportedException fnsEx)
            {
                // Feature not supported on device
                await Device.InvokeOnMainThreadAsync(() =&gt; MessageLabel.Text = "The check-in feature is not supported on your device.");
            }
            catch (Exception ex)
            {
                // Handle exception that may have occurred in geocoding
                await Device.InvokeOnMainThreadAsync(() =&gt; MessageLabel.Text = "We are unable to retrieve the client's location.");
            }
        }
     }
     catch (FeatureNotSupportedException fnsEx)
     {
         // Handle not supported on device exception
         await Device.InvokeOnMainThreadAsync(() =&gt; MessageLabel.Text = "The check -in feature is not supported on your device.");
    }
    catch (FeatureNotEnabledException fneEx)
    {
        // Handle not enabled on device exception
        await Device.InvokeOnMainThreadAsync(() =&gt; MessageLabel.Text = "The check-in feature is not enabled on your device.");
    }
    catch (PermissionException pEx)
    {
        // Handle permission exception
        await Device.InvokeOnMainThreadAsync(() =&gt; MessageLabel.Text = "You need to grant location permission for the check-in feature to work on your device.");
    }
    catch (Exception ex)
    {
        // Unable to get location
        await Device.InvokeOnMainThreadAsync(() =&gt; MessageLabel.Text = "We are unable to retrieve your location.");
    }
}
</code></pre>
<p>Let me break down what’s going on here. We’re constructing a GeolocationRequest object with medium accuracy. That should allow check-in accuracy within 100 to 500 meters on Android and within 100 meters on iOS. Next we get the user’s location. Note that GetLocationAsync is an asynchronous call, as are many of these location functions, so our CheckIn function must be asynchronous. There are two significant consequences. One, we should probably use a spinner on the interface in case these calls take long. Since this is just a demo, I’ll skip that. Two, we need to remember to switch back to the main thread when we’re ready to update the interface.</p>
<p>Moving further into the code, we use GetLocationsAsync to convert an address into an IEnumerable of Locations, of which we’ll grab the first object in the list. This is how I’m converting an address to a set of latitude and longitude coordinates. I’m using the address of HangZone. I don’t think you can beat me for Mayor of HangZone, so choose whatever other location you’d like!</p>
<p>Finally, we do our distance test within 0.6 kilometers, since the medium accuracy is good within 500 meters at worst. If the user is close enough, we update the MessageLabel for a successful check-in. If not, we tell the user to move closer. We also address a bunch of potential errors—everything from internet errors to location permission errors.</p>
<h2>Hook it Up to the Check-In Button</h2>
<p>We should probably test this out to make sure everything is working correctly. We’ll have to hook up our button first, though. Go back to MainPage.xaml. Add a clicked property to the Button.</p>
<pre><code>Clicked="CheckInButton_Clicked"
</code></pre>
<p>Now go back to the code-behind and implement it. The function may have been auto-populated when you filled out the xaml, but make sure to make the function asynchronous.</p>
<pre><code>private async void CheckInButton_Clicked(object sender, EventArgs e)
{
    await Task.Run(CheckIn);
}
</code></pre>
<p>Now give it a run.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Missing-Permissions.png"><img decoding="async" class="alignnone size-medium wp-image-2101" src="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Missing-Permissions-139x300.png" alt="Check In Missing Permissions" width="139" height="300" srcset="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Missing-Permissions-139x300.png 139w, https://hangzone.com/wp-content/uploads/2020/03/Check-In-Missing-Permissions-473x1024.png 473w, https://hangzone.com/wp-content/uploads/2020/03/Check-In-Missing-Permissions.png 1125w" sizes="(max-width: 139px) 100vw, 139px" /></a></p>
<h2>Prompt the User for Location Permission</h2>
<p>Well, apparently my iPhone still needs location permission. What’s the deal with that? No worries. This is a good chance to point out a pesky issue with this library that might trip you up. The Xamarin Essentials library will automatically prompt the user to give the app location permission if you call GetLocationAsync without having the proper permission. Unfortunately, we’re running our check-in code on a background thread. While that makes sense for all of these asynchronous location calls, it prevents the library from being able to display the permission request on the UI.</p>
<p>The easiest fix is just to execute our CheckIn function on the main thread. Within the CheckInButton_clicked function, wrap our call to the CheckIn function like so.</p>
<pre><code>Await Device.InvokeOnMainThreadAsync(() =&gt; CheckIn());
</code></pre>
<p>Try to check-in again. You should see a pop-up asking to use your location. Cool! Now it works!</p>
<p><a href="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Permissions.png"><img decoding="async" class="alignnone size-medium wp-image-2102" src="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Permissions-139x300.png" alt="Check In Permissions" width="139" height="300" srcset="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Permissions-139x300.png 139w, https://hangzone.com/wp-content/uploads/2020/03/Check-In-Permissions-473x1024.png 473w, https://hangzone.com/wp-content/uploads/2020/03/Check-In-Permissions.png 1125w" sizes="(max-width: 139px) 100vw, 139px" /></a></p>
<p>Note that while this solution is alright for a demo, we really would rather run the CheckIn function on a background thread. Ideally, we could check for location permission before calling the CheckIn function, then let it do its work on a background thread. That’s just something to think about for when you do this in a real app!</p>
<h2>Count the Check-ins</h2>
<p>Ultimately, we would like to track total check-ins on a server. That way, we know how all of your friends are doing, and we can assign a mayor. For part 1 of this tutorial, we’re skipping the server. Therefore, we’ll store the value locally in Preferences. You get access to the Preferences functionality as part of the Xamarin Essentials library, which we’ve already included for the location code. We’ll also use a local int to keep track of the check-in number.</p>
<p>At the top of your MainPage class definition, just above your constructor, add the following code.</p>
<pre><code>private int CheckInCount;
</code></pre>
<p>Inside the MainPage constructor, add the following below InitializeComponenet().</p>
<pre><code>CheckInCount = Preferences.Get("checkInCount", 0);
CountLabel.Text = "Check-In Count: " + CheckInCount;
</code></pre>
<p>This will ensure that our app loads the check-in count from our previous session. If there wasn’t a previous session, we default to zero total check-ins.</p>
<p>Now inside the CheckIn function, find the if statement that ensures the check-in is within 0.6 meters of the location. Right after we update the MessageLabel, add the following lines.</p>
<pre><code>CheckInCount++;
await Device.InvokeOnMainThreadAsync(() =&gt; CountLabel.Text = "Check-In Count: " + CheckInCount);
Preferences.Set("checkInCount", CheckInCount)
</code></pre>
<p>This increments our total check-ins, updates the label, and puts the value in persistent storage so we’ll have it next time we open the app.</p>
<p>Run the app, and save your first check-in!</p>
<p><a href="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Success.png"><img decoding="async" class="alignnone size-medium wp-image-2103" src="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Success-139x300.png" alt="Check In Success" width="139" height="300" srcset="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Success-139x300.png 139w, https://hangzone.com/wp-content/uploads/2020/03/Check-In-Success-473x1024.png 473w, https://hangzone.com/wp-content/uploads/2020/03/Check-In-Success.png 1125w" sizes="(max-width: 139px) 100vw, 139px" /></a></p>
<h2>Limit Check-In Frequency</h2>
<p>Alright. So far so good. You arrive at the location, you tap the button, and you’re checked-in. But what happens if you tap the button again? Egads! You’ve checked-in twice! This will compromise the integrity of the mayorship if users take advantage of this exploit!</p>
<p>Let’s put a 12 hour limit on how often you can check-in to a location. We can store a timestamp whenever you successfully check-in. On subsequent attempts, we’ll just make sure the current time is at least 12 hours later than the last time. If it is, we’ll process the check-in and update the timestamp.</p>
<p>We’ll update the CheckInButton_Clicked to look like this.</p>
<pre><code>private async void CheckInButton_Clicked(object sender, EventArgs e)
{
    var lastTimeStamp = Preferences.Get("lastTimeStamp", new DateTime(2010,1,1));

    if ((DateTime.Now - lastTimeStamp).TotalHours &gt;= 12)
    {
        await Device.InvokeOnMainThreadAsync(() =&gt; CheckIn());
    }
    else
    {
    await Device.InvokeOnMainThreadAsync(() =&gt; MessageLabel.Text = "You haven't waited 12 hours since your last check-in.");
    }
}
</code></pre>
<p>We also need to add the code for updating the timestamp on successful check-ins. Inside the CheckIn function, add the following code just below where we saved CheckInCount in Preferences.</p>
<pre><code>Preferences.Set("lastTimeStamp", DateTime.Now);
</code></pre>
<h2><a href="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Frequency-Limit.png"><img decoding="async" class="alignnone size-medium wp-image-2099" src="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Frequency-Limit-139x300.png" alt="Check In Frequency Limit" width="139" height="300" srcset="https://hangzone.com/wp-content/uploads/2020/03/Check-In-Frequency-Limit-139x300.png 139w, https://hangzone.com/wp-content/uploads/2020/03/Check-In-Frequency-Limit-473x1024.png 473w, https://hangzone.com/wp-content/uploads/2020/03/Check-In-Frequency-Limit.png 1125w" sizes="(max-width: 139px) 100vw, 139px" /></a></h2>
<h2>Conclusion</h2>
<p>You’ve got a fully functional check-in app! It’s really only effective as a sort of one-player game at the moment, since you’re not competing against anyone else on the server. We’ll add that code another time. For now, perhaps you can use it to track how often you go the gym. Keep an eye open for part two! Until then, thanks for reading the HangZone blog.</p>
<p>The post <a href="https://hangzone.com/how-to-make-a-location-check-in-app-in-xamarin-forms-part-one/">How to Make a Location Check-In App in Xamarin.Forms Part One</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Local Security Measures in Xamarin.Forms</title>
		<link>https://hangzone.com/local-security-measures-xamarin-forms/</link>
		
		<dc:creator><![CDATA[Judson Bandy]]></dc:creator>
		<pubDate>Fri, 28 Feb 2020 14:32:24 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=2085</guid>

					<description><![CDATA[<p>There are many types of mobile apps where security is of the utmost importance. When dealing with banking, payment, health, or other sensitive information, it is crucial to keep data ...</p>
<p>The post <a href="https://hangzone.com/local-security-measures-xamarin-forms/">Local Security Measures in Xamarin.Forms</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>There are many types of mobile apps where security is of the utmost importance. When dealing with banking, payment, health, or other sensitive information, it is crucial to keep data safe. There may even be legal ramifications for exposing user data. This conversation typically begins with a secure login, protecting data in transit, and data access. While these are the most important aspect, we’re going to look at a different aspect of protection.</p>
<p>How can we best protect users from exposing data locally? Yes, a secure login is the first step, but what if a user logs in, and then exits the app without hard closing it. If someone else uses their phone, are we able to protect the data? Also, what if they put their phone down with the app still up? There are several little steps that can be done to help the user protect their data. Let’s look at a few ways to help further secure your apps.</p>
<h2>Create the Xamarin.Forms Project</h2>
<p>First, we will create a Xamarin.Forms project. I am doing it on a Windows computer, but it can also be done on Visual Studio for Mac. Open Visual Studio and click to create a new project. Select Mobile App (Xamarin.Form) in C# and press Next. I’m going to name my project, “XamarinSecurityExample.” Then, press Create. On the template selection, we are going with Blank, and Android and iOS for platforms. The app is now created. Now, we are just going to make a simple two page application to show how our security features can be plugged in.</p>
<p>Open MainPage.xaml. This is the first page that opens when you start the app and it will be our mock login page. Start by deleting everything inside the StackLayout and add the following.</p>
<pre><code>&lt;StackLayout&gt;
    &lt;Label Text="Login Page"
      FontSize="Medium"
      HorizontalOptions="Center"
      VerticalOptions="CenterAndExpand" /&gt;
    &lt;Button Text="Very Secure Login"
      HorizontalOptions="Center"
      VerticalOptions="CenterAndExpand"
      Clicked=“LoginButton_Clicked”/&gt;
&lt;/StackLayout&gt;</code></pre>
<p>This code simply puts a title of Login Page in the top half of the screen, and a button in the bottom half of the page. The button has a function that we will define in the codebehind when it is clicked.</p>
<p>Let’s do that now. Open the MainPage.xaml.cs. Define our new method like this.</p>
<pre><code>private void LoginButton_Clicked(object sender, EventArgs e)
{
    Application.Current.MainPage = new SecurePage();
}</code></pre>
<p>This will navigate to our a new page when the button is pressed. Now, we have to create the new page.</p>
<p>Right click on our shared project, and click Add-&gt;New Item… Select Content Page, and name it, “SecurePage.xaml.” Let’s add some content to SecurePage.xaml. Replace the StackLayout with the following.</p>
<pre><code>&lt;StackLayout&gt;
    &lt;Label Text="Very private information that should not be seen"
      FontSize="Medium"
      HorizontalOptions="Center"
      VerticalOptions="CenterAndExpand" /&gt;
    &lt;Button Text="Logout"
      HorizontalOptions="Center"
      VerticalOptions="CenterAndExpand"
      Clicked="LogoutButton_Clicked"/&gt;
&lt;/StackLayout&gt;</code></pre>
<p>The UI on this page is just like our login page. There is a label at the top that talks about all of our secure data, and then, a button at the bottom. Again, we need to go to the codebehind to define our Clicked method. Open SecurePage.xaml.cs, and add the following method.</p>
<pre><code>private void LogoutButton_Clicked(object sender, EventArgs e)
{
    Application.Current.MainPage = new MainPage();
}</code></pre>
<p>Our base app is now finished. It’s clearly nothing to write home about, but it will get the job done for our example. There is a login page, and a page that you navigate to after “logging in” that has secure information.</p>
<h2>Auto Logout When Returning to App</h2>
<p>The first thing we’ll tackle is auto logout when returning to the app. This will happen if a user is logged in, exits the app (but does not hard close), then clicks to reopen the app. If the user is only gone for a short period of time, it should be fine to keep the user logged in. Otherwise, we certainly want to auto logout the user. Let’s jump back into the code. This one should be fairly straightforward. First, open App.xaml.cs. Add the following code.</p>
<pre><code>public static bool IsUserLoggedIn = false;
private DateTime? SleepTime = null;

protected override void OnSleep()
{
    SleepTime = DateTime.Now;
}

protected override void OnResume()
{
    if (SleepTime != null)
    {
        if (DateTime.Now.Subtract((DateTime)SleepTime) &gt; TimeSpan.FromSeconds(120))
        {
            LogOutUser();
        }
    }
}

public static void LogOutUser()
{
    if (!IsUserLoggedIn)
        return;

    Application.Current.MainPage = new MainPage();
}</code></pre>
<p>To start with, we added two variables. One is SleepTime, which keeps track of time away from the app, and the other is IsUserLoggedIn, which tracks if the user is logged in. Then, we override the OnSleep method to set the SleepTime variable to the time that the app went to sleep. Next, we override OnResume. There, we check to see how long we have been away from the app, and if it was longer than 2 minutes, we call the LogOutUser method. In that method, we check to see that the user is logged in, and if so, we return the app to the login page.</p>
<p>Finally, we need to adjust the IsUserLoggedIn method when the user logs in and out. In LoginButton_Clicked in MainPage.xaml.cs, add the following.</p>
<pre><code>App.IsUserLoggedIn = true;</code></pre>
<p>Then, in LogoutButton_Clicked in SecurePage.xaml.cs, add this.</p>
<pre><code>App.IsUserLoggedIn = false;</code></pre>
<p>That’s it! The user will be able to resume their session if they return to the app within 2 minutes of closing it. Otherwise, they will be auto logged out and have to log in again to access their data.</p>
<h2>Auto Logout for Inactivity</h2>
<p>That is an excellent solution for a user that exits the app and then returns, but what if the user leaves their phone with the app still up? Sensitive data could easily be exposed. To try to prevent this, we would like to auto logout the user after a certain amount of inactivity. Unfortunately, this code is a little more complicated. This is due to the fact that user input is detected in the Android and iOS sections, respectively, depending on your device type. Let’s get started and try the Android version first.</p>
<h2>Android Inactivity</h2>
<p>Open MainActivity.cs in the Android section of the project. Our strategy will be to throw a popup after a certain amount of inactivity, and then if there is no activity, log the user out. Let’s start by defining variables for those amounts of time and for a handler.</p>
<pre><code>public static long DISCONNECT_WARNING = 240000; // 4 min = 4 * 60 * 1000ms = 240000
public static long DISCONNECT_TIMEOUT = 60000; // 1 min = 1 * 60 * 1000ms = 60000
public Handler DisconnectWarningHandler;</code></pre>
<p>We create 2 static variables. First, the warning amount of time will be 4 minutes, and then the actual logout will occur if there is no activity for another minute. Next, add the following code to the OnCreate method.</p>
<pre><code>DisconnectWarningHandler = new Handler(new WarningHandlerICallback(this));</code></pre>
<p>Now, we need to define the WarningHandlerICallback class. This is still inside the MainActivity class. Then, we will add our code to start and stop the inactivity handlers.</p>
<pre><code>public Handler disconnectWarningHandler;

public class WarningHandlerICallback : Java.Lang.Object, Handler.ICallback
{
    private MainActivity mainActivity;

    public WarningHandlerICallback(MainActivity mainActivity)
    {
        this.mainActivity = mainActivity;
    }

    public bool HandleMessage(Message msg)
    {
        App.WarnUserAboutInactivity();
        mainActivity.resetDisconnectTimer();
        return true;
    }
}

System.Action LogoutAction = () =&gt;
{
    App.LogOutUser();
};

public void resetWarningTimer()
{
    DisconnectWarningHandler.RemoveMessages(1);
    DisconnectWarningHandler.SendEmptyMessageDelayed(1, DISCONNECT_WARNING);
}

public void stopWarningTimer()
{
    DisconnectWarningHandler.RemoveMessages(1);
}

public void resetDisconnectTimer()
{
    DisconnectWarningHandler.RemoveCallbacks(LogoutAction);
    DisconnectWarningHandler.PostDelayed(LogoutAction, DISCONNECT_TIMEOUT);
}

public void stopDisconnectTimer()
{
    DisconnectWarningHandler.RemoveCallbacks(LogoutAction);
}

protected override void OnStop()
{
    base.OnStop();
    stopWarningTimer();
    stopDisconnectTimer();
}

protected override void OnResume()
{
    base.OnResume();
    resetWarningTimer();
    stopDisconnectTimer();
}

public override void OnUserInteraction()
{
    resetWarningTimer();
    stopDisconnectTimer();
}</code></pre>
<p>That’s a lot to take in, but we’ll try to explain it. OnUserInteraction is where we are able to register user activity. When the user does anything, we reset our warning timer and stop our disconnect timer. Only one of these timers is ever actually on a once, but this does a full reset if one or none of the timers are running. The ResetWarningTimer method calls to send an empty delayed message for the disconnect warning length of time that we set. If that time passes with no activity, HandleMessage in our WarningHandlerICallback class gets called. There, we call the App.xaml.cs file to throw a warning popup, and we start our disconnect timer.</p>
<p>The disconnect timer does a PostDelayed for the length of time that we set for disconnect. This performs an action that we call LogoutAction, which calls the App.xaml.cs LogOutUser method that we created earlier. OnStop and OnResume are also overridden so that the timers are reset when you exit and reenter the app.</p>
<p>Finally, go back to App.xaml.cs. We have to add the inactivity warning method that we called from MainActivity.</p>
<pre><code>public static async void WarnUserAboutInactivity()
{
    if (!IsUserLoggedIn)
        return;

    await Application.Current.MainPage.DisplayAlert("Inactivity Warning", "Are you still there? If you don't return to the application, you will be logged out.", "OK");
}</code></pre>
<p>As you can see, if the user is logged in, they will receive a popup warning that they will soon be logged out for inactivity. That’s it. The Android solution is done.</p>
<p>***A quick word of warning. I would strongly suggest using custom popups instead of the built in DisplayAlert popups. There is no way to programmatically close these popups, which can be problematic in a number of different situations. For instance, you would likely want to close the warning popup about the impending logout once the user is actually logged out. There also can be issues with multiple popups being displayed at once. Finally, clicking the button on the system popup doesn’t register through OnUserInteraction (you can easily use MessagingCenter to alert the MainActivity class when/if the popup is dismissed though).</p>
<h2>iOS Inactivity</h2>
<p>Next, we’ll set up the iOS inactivity check. In the iOS section of the project, open AppDelegate.cs. Add the following code.</p>
<pre><code>public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    …

    <del>return base.FinishedLaunching(app, options);</del>
    var success = base.FinishedLaunching(app, options);

    var allGesturesRecognizer = new AllGesturesRecognizer(delegate
    {
        ResetIdleTimer();
    });

    this.Window.AddGestureRecognizer(allGesturesRecognizer);

    if (IdleTimer == null)
    {
        IdleTimer = NSTimer.CreateRepeatingScheduledTimer(TimeSpan.FromSeconds(10), delegate
        {
            LogOutMethod();
        });
    }

    return success;
}

private NSTimer IdleTimer = null;
private bool TouchDetected = false;
private int LogoutCounter = 0;
private bool IsInactivityPopupDisplayed = false;

[Export("applicationWillResignActive:")]
override public void OnResignActivation(UIApplication uiApplication)
{
    IdleTimer.Invalidate();
    IdleTimer = null;
    LogoutCounter = 0;

    base.OnResignActivation(uiApplication);
}

[Export("applicationDidBecomeActive:")]
override public void OnActivated(UIApplication uiApplication)
{
    if (IdleTimer == null)
    {
        IdleTimer = NSTimer.CreateRepeatingScheduledTimer(TimeSpan.FromSeconds(10), delegate
        {
            LogOutMethod();
        });
    }

    base.OnActivated(uiApplication);
}

private void ResetIdleTimer()
{
    TouchDetected = true;
}

private void LogOutMethod()
{
    if (TouchDetected)
    {
        if (IsInactivityPopupDisplayed)
        {
            IsInactivityPopupDisplayed = false;
        }

        LogoutCounter = 0;
        TouchDetected = false;
        return;
    }

    LogoutCounter++;

    if (LogoutCounter == 24) //24 4 min = 4 * (6 * 10sec)
    {
        App.WarnUserAboutInactivity();
    }
    else if (LogoutCounter &gt;= 30) //30 5 min = 5 * (6 * 10sec)
    {
        App.LogOutUser();
        LogoutCounter = 0;
    }
}

class AllGesturesRecognizer : UIGestureRecognizer
{
    public delegate void OnTouchesEnded();

    private OnTouchesEnded touchesEndedDelegate;

    public AllGesturesRecognizer(OnTouchesEnded touchesEnded)
    {
        this.touchesEndedDelegate = touchesEnded;
    }

    public override void TouchesEnded(NSSet touches, UIEvent evt)
    {
        this.State = UIGestureRecognizerState.Failed;
        this.touchesEndedDelegate();

        base.TouchesEnded(touches, evt);
    }
}</code></pre>
<p>This is all of the iOS code at once. Let’s try to break it down. In the FinishedLaunching method, we create a GestureRecognizer that calls ResetIdleTimer whenever touch is detected. The ResetIdleTimer method sets TouchDetected, an instance variable, to true. Next in FinishedLaunching, a timer is created that calls LogOutMethod every ten seconds. If TouchDetected is true, the LogoutCounter is reset. Otherwise, the inactivity warning method in App.xaml.cs is called after 4 minutes, and the logout method is called after an additional minute of inactivity.</p>
<p>Test it out, and you’ll see an inactivity warning after you sit around on the secure page for a while. Start interacting with the application and nothing will happen. Otherwise, you will be auto logged out after another minute of inactivity.</p>
<h2>Obscure Information When Looking at Recent Apps</h2>
<p>The final issue we want to tackle is the view that shows recently open apps. On iOS this is called the App Switcher, and on new iPhone and iPad devices, you get to this screen by swiping up from the bottom to the middle of the screen and holding. On Android, it’s the recent apps overview, and you get there by pressing the square button on older devices or a quick swipe from the bottom on newer devices.</p>
<p>Regardless of platform, this view shows recently open apps that are currently in the background. Each app shows a picture of your last screen in the app. You are able to click on the picture to reenter an app or you can hard close the app. The issue here is information potentially shown on the picture. If we were on a page containing sensitive information when we exited the app, that sensitive information would now be visible. To prevent this, we are going to obscure what the user sees.</p>
<h2>Obscure Display in Android</h2>
<p>Let’s start with Android. This time it’s the simple option. In the OnCreate method in MainActivity.cs, add the following line.</p>
<pre><code>Window.SetFlags(WindowManagerFlags.Secure, WindowManagerFlags.Secure);</code></pre>
<p>That’s it. If you go to the recent apps overview, you will see that it shows a white screen with no content, which you can see below. We’ve done it! Besides hiding the display, this flag also prevents the user from taking screenshots inside the app.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2020/02/Blog-Local-Security-Xamarin-Secure-Flag.png"><img decoding="async" class="alignnone size-medium wp-image-2086" src="https://hangzone.com/wp-content/uploads/2020/02/Blog-Local-Security-Xamarin-Secure-Flag-178x300.png" alt="Blog Local Security Xamarin Secure Flag" width="178" height="300" srcset="https://hangzone.com/wp-content/uploads/2020/02/Blog-Local-Security-Xamarin-Secure-Flag-178x300.png 178w, https://hangzone.com/wp-content/uploads/2020/02/Blog-Local-Security-Xamarin-Secure-Flag-608x1024.png 608w, https://hangzone.com/wp-content/uploads/2020/02/Blog-Local-Security-Xamarin-Secure-Flag.png 671w" sizes="(max-width: 178px) 100vw, 178px" /></a></p>
<h2>Obscure Display in iOS App Switcher</h2>
<p>Open the AppDelegate.cs file, and we will get started with the iOS version. First add a private instance variable of UIView.</p>
<pre><code>private UIView _view;</code></pre>
<p>Next, we will add lines to the OnResignActivation and OnActivated methods that we overrode for the inactivity check.</p>
<pre><code>override public void OnResignActivation(UIApplication uiApplication)
{
    var window = UIApplication.SharedApplication.KeyWindow;
    var rect = UIScreen.MainScreen.Bounds;
    _view = new UIView { Frame = rect };
    _view.BackgroundColor = UIColor.FromRGB(60,179,113);
    window.AddSubview(_view);

    …
}

override public void OnActivated(UIApplication uiApplication)
{
    …

    if (_view != null)
    {
        _view.RemoveFromSuperview();
        _view.Dispose();
    }

    …
}</code></pre>
<p>When we resign active, we instantiate our UIView, make it the size of the screen, give it a green color, and then add it as a subview over our app. When the app is activated, we remove the view. That’s it. Test it out and you will see a solid green color covering the app’s screen when the App Switcher is up.</p>
<h2>Conclusion</h2>
<p>Great work! We were able to look at several ways to improve local security in our apps. First, we added an auto logout for when the app is in the background for too long. Then, we added an auto logout if the user is inactive inside the app for five minutes. Finally, we secured any sensitive information that could be displayed when the App Switcher is displayed. Good luck implementing these security concepts into your next app that requires dealing with delicate information. As always, thanks for reading the HangZone blog.</p>
<p>The post <a href="https://hangzone.com/local-security-measures-xamarin-forms/">Local Security Measures in Xamarin.Forms</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>An Analysis of Mario Kart Tour</title>
		<link>https://hangzone.com/an-analysis-of-mario-kart-tour/</link>
		
		<dc:creator><![CDATA[Tyler Bandy]]></dc:creator>
		<pubDate>Thu, 30 Jan 2020 16:13:18 +0000</pubDate>
				<category><![CDATA[Game Industry]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=2075</guid>

					<description><![CDATA[<p>If you follow our blog, you know there are two topics we never miss a chance to cover: mobile gaming monetization techniques and Nintendo’s mobile game releases. Now that Mario ...</p>
<p>The post <a href="https://hangzone.com/an-analysis-of-mario-kart-tour/">An Analysis of Mario Kart Tour</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>If you follow our blog, you know there are two topics we never miss a chance to cover: mobile gaming monetization techniques and Nintendo’s mobile game releases. Now that Mario Kart Tour has been out for a few months, this seems like a good opportunity to evaluate how the game is performing. Let’s dig into the gameplay itself, the monetization approach, the financial performance, and the audience reception.</p>
<h2>The Gameplay</h2>
<p>First of all, let’s touch on the gameplay. Power sliding and mini turbos are such a critical part of the modern Mario Kart experience, and I was really skeptical of how Mario Kart Tour could implement this mechanic. It turns out they really did a great job with this! The manual control scheme retains the familiar left-to-right toggling throughout a turn to manage your drift. Some of the other mechanics are simplified. Your driver automatically accelerates forward. There are no brakes or reverse. The game even employs some automatic assistance to redirect your driver back on the course. Ultimately, these are all reasonable compromises for the limitations of the mobile interface to ensure that the mini turbo and drifting mechanics all feel as tight as possible. It works quite nicely!</p>
<p>There’s a pretty nice flow of new content as well. Nintendo releases some new courses and characters every two weeks. There are new challenges and a ranking tournament each week. The multiplayer is still in beta, but that’s something to look forward to. The game is still relatively new, so I suspect Nintendo had a lot of these new courses and characters stockpiled to gradually release. Nevertheless, hopefully they can keep up this pace of content release.</p>
<h2>The Monetization Approach</h2>
<p>Mario Kart Tour uses the now quite familiar gacha mechanic to get new characters, karts, and gliders. If you’re not familiar with gacha, <a href="https://hangzone.com/gacha-mobile-app-monetization/">check out this old post where we discussed the technique</a>. Essentially, you’re using in-game currency, in this case rubies, to pay for a chance to pull a random character, kart, or glider out of a pipe. This is the only way to get most characters, karts, and gliders, but you can also acquire a limited set of the roster with gold (the less rare in-game currency). Collecting duplicates makes an item stronger. Oddly enough, you don’t even get to play with Mario to start the game, unless you pull him from the pipe or buy him in the store with gold!</p>
<p>Nintendo’s two revenue streams are selling rubies and offering a monthly Gold Pass subscription for $5.00. The Gold Pass offers more challenges every two weeks, a faster speed class, plus it gives you a bunch of rubies, gold, characters, and such. Compared to buying rubies directly in the store, it’s a terrific deal. Or perhaps viewed another way, the rubies in the store seem quite expensive. There are always a couple special bundles going on to buy a character plus some rubies, with a not so rare character in a $20 bundle, and a rarer one in a $40 bundle. Even with the added kicker, rubies are still fairly expensive. For what it’s worth, I’m a Gold Pass subscriber, but I haven’t made any other purchases.</p>
<p>Subscriptions have become increasingly common in mobile games in the last few months. While the freemium mindset has traditionally been to focus on whales that are going to spend hundreds or possibly thousands of dollars, there’s a growing stigma against this practice, at least in some circles. The subscriptions are relatively cheap. Mario Kart Tour’s $5.00 per month price translates to $60.00 per year, which is about the price of a console game. That sounds reasonable to a lot of consumers, including myself, who don’t usually buy consumable in-app purchases. Subscriptions also charge the consumer indefinitely until they unsubscribe, so there’s a nice stickiness to the revenue stream. Overall, it seems like a smart approach.</p>
<h2>The Financial Performance</h2>
<p>Sensor Tower compiled the downloads for the first 98 days after release of Nintendo’s mobile titles.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2020/01/Nintendo-First-98-Days-Downloads.png"><img decoding="async" class="alignnone wp-image-2076 size-large" src="https://hangzone.com/wp-content/uploads/2020/01/Nintendo-First-98-Days-Downloads-1024x687.png" alt="Nintendo First 98 Days Downloads" width="800" height="537" srcset="https://hangzone.com/wp-content/uploads/2020/01/Nintendo-First-98-Days-Downloads-1024x687.png 1024w, https://hangzone.com/wp-content/uploads/2020/01/Nintendo-First-98-Days-Downloads-300x201.png 300w, https://hangzone.com/wp-content/uploads/2020/01/Nintendo-First-98-Days-Downloads.png 1712w" sizes="(max-width: 800px) 100vw, 800px" /></a></p>
<p>As you can see, Mario Kart Tour is the clear winner. The game generated more than 3 times as many downloads as Super Mario Run, the second most downloaded Nintendo game for the opening 98 days. In fact, Mario Kart Tour was the most downloaded game in the App Store for all of 2019, even though it came out toward the end of the year. There’s no questioning the broad appeal and reputation for quality that the Mario Kart franchise has built up over the last couple decades.</p>
<p>Revenue is also good, but not as dominant. This time, Mario Kart Tour comes in second, earning about 60% as much as Fire Emblem Heroes. The fact that Fire Emblem can outpace Mario Kart on revenue despite having less than 10% as many users is truly a testament to how well Fire Emblem’s revenue model works.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2020/01/Nintendo-First-98-Days-Revenue.png"><img decoding="async" class="alignnone wp-image-2077 size-large" src="https://hangzone.com/wp-content/uploads/2020/01/Nintendo-First-98-Days-Revenue-1024x669.png" alt="Nintendo First 98 Days Revenue" width="800" height="523" srcset="https://hangzone.com/wp-content/uploads/2020/01/Nintendo-First-98-Days-Revenue-1024x669.png 1024w, https://hangzone.com/wp-content/uploads/2020/01/Nintendo-First-98-Days-Revenue-300x196.png 300w, https://hangzone.com/wp-content/uploads/2020/01/Nintendo-First-98-Days-Revenue.png 1720w" sizes="(max-width: 800px) 100vw, 800px" /></a></p>
<p><a href="https://hangzone.com/a-breakdown-of-dr-mario-worlds-financial-performance/">We’ve touched on Fire Emblem’s financial performance in the past while analyzing Dr Mario World</a>, but let’s discuss why it differs so much from Mario Kart Tour. First, we know gacha games perform better in Japan than in the US. There is less stigma against spending money on mobile games in Japan, so relatively niche titles that target a Japanese user base can outperform franchises with broader global appeal. Second, Mario Kart appeals to a wider range of ages, including many children who don’t have money to spend on games. That’s going to drive revenue per user down. Lastly, RPGs lend themselves to gacha mechanics better than racing games. RPG players are used to leveling things up, which is a critical part of the gacha experience. You can continue to layer in new complexities to the game so the player base always has something to chase. This keeps gamers occupied (and spending money) for years. Mario Kart has traditionally catered to a more casual audience. Sure, you can pull for new drivers and level them up, but it remains to be seen whether Nintendo can add enough complexity to the game to keep players hooked for the long-term.</p>
<h2>The Audience Reception</h2>
<p>Mario Kart Tour currently has a 4.8 in the App Store and a 4.2 on Google Play, so it’s safe to say that most players are happy with it. I will say the online community sure does complain a lot though (see Reddit). On the one hand, I think this has to do with Mario Kart’s broad appeal. A lot of Mario Kart fans who would not normally play gacha games are playing Mario Kart Tour, and they don’t like the gacha mechanics.</p>
<p>There’s more to it than that though. Mario Kart Tour puts a tremendous amount of emphasis on the weekly ranking tournament. That’s my main focus in the game. This puts you in direct competition with other users. This is different from other successful gacha titles, such as Puzzle and Dragons, where the focus is mostly on the single player experience (Player vs Environment instead of Player vs Player, to use the industry jargon). Consequently, whales are generally celebrated on the Puzzle and Dragons subreddit for their crazy team builds and financial support of the game. The Mario Kart Tour subreddit is mostly filled with hate toward whales for paying to win the ranking tournaments.</p>
<p>The tournament emphasis puts Nintendo in a tricky spot. They don’t want to make the game too pay-to-win, but they also need to make sure their paying customers see value in what they’re buying. This whole matter is complicated by the fact that the game actually does have quite a bit of skill. Maintaining a combo over an entire course is very difficult in some levels, and only doable by the top players. Nonetheless, many weaker players seem to think they only lose because they’re being outspent, which frustrates stronger players, and generally makes for a contentious community.</p>
<h2>Conclusion</h2>
<p>Mario Kart Tour is off to a reasonably strong start. The downloads are tremendous. The financial performance is quite good, though not as strong per user as the top Japanese gacha titles. That’s to be expected though. It will be interesting to see how the revenue develops from here.</p>
<p>The community health doesn’t seem wonderful for the long-term viability of the game, but that can be fixed. When Puzzle and Dragons does ranking dungeons, sometimes they use fixed teams. This way players can’t complain about pay-to-win mechanics. It’s all about skill (and some luck). Mario Kart Tour might be well off to do the same thing from time to time for its ranking tournaments. That would be enough to silence grumblings from the weaker players and show them that they’re not only losing because of pay-to-win mechanics. That would make the stronger players happier. It would also presumably allow the spenders to go about their spending without getting hassled by the community so much. We’ll see if Nintendo follows this route. Either way, they’ve got a hit on their hands! Let’s hope they make the most of it.</p>
<p>The post <a href="https://hangzone.com/an-analysis-of-mario-kart-tour/">An Analysis of Mario Kart Tour</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Calculate the Implied RGB Color for Opacity Changes</title>
		<link>https://hangzone.com/calculate-implied-rgb-color-opacity-changes/</link>
		
		<dc:creator><![CDATA[Tyler Bandy]]></dc:creator>
		<pubDate>Tue, 31 Dec 2019 22:59:35 +0000</pubDate>
				<category><![CDATA[Developer Insights]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=2064</guid>

					<description><![CDATA[<p>Subtle usage of opacity changes can give your app a polished look. For instance, open the iOS Settings app. Tap the General cell, and look at the top toolbar. The ...</p>
<p>The post <a href="https://hangzone.com/calculate-implied-rgb-color-opacity-changes/">How to Calculate the Implied RGB Color for Opacity Changes</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Subtle usage of opacity changes can give your app a polished look. For instance, open the iOS Settings app. Tap the General cell, and look at the top toolbar. The button to return to the previous page is written in blue. If you tap it, the button’s opacity decreases. This isn’t an over-the-top animation, but just enough to give you some feedback for your interaction.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2019/12/Settings-Press-Opacity.png"><img decoding="async" class="alignnone size-medium wp-image-2065" src="https://hangzone.com/wp-content/uploads/2019/12/Settings-Press-Opacity-292x300.png" alt="Settings Press Opacity" width="292" height="300" srcset="https://hangzone.com/wp-content/uploads/2019/12/Settings-Press-Opacity-292x300.png 292w, https://hangzone.com/wp-content/uploads/2019/12/Settings-Press-Opacity-998x1024.png 998w" sizes="(max-width: 292px) 100vw, 292px" /></a></p>
<p>The native iOS controls manage this particular navigation animation for you, but there will be many times when you’re using custom controls where you might want to manually manage opacity. That’s usually no problem, but occasionally you’ll find controls that don’t allow you to change the opacity. Fear not! This just requires some extra math. Let’s get to it!</p>
<h2>The Formula</h2>
<p>We’ll need to think of colors in terms of RGB values for the purposes of this calculation. If you’re using hex codes or predefined system colors, you’ll need to convert them to RGB values to proceed with this calculation. Once you’ve got the red, green, and blue values for your color, use the following formula on each R, G, and B units.</p>
<pre><code>adjustedColor = opacity * foregroundColor + (1 - opacity) * backgroundColor
</code></pre>
<p>Let’s start with a quick sanity check to make sure this makes sense. Let’s say we have a black button against a white background. We want it to change to 50% opacity when it’s pressed. The RGB for black is (0, 0, 0) and white is (255, 255, 255). Consequently, the formula looks the same for each of the R, G, and B channels.</p>
<pre><code>.50 * 0 + (1 - .50) * 255 = 127.5
</code></pre>
<p>We’ll round that up and get (128, 128, 128). That’s gray, and that’s the color we want. This equation will work with any foreground, background, and opacity combination that you need!</p>
<h2>Backing Out the iOS Opacity</h2>
<p>Apple is known for their polished app design, so let’s calculate what opacity they’re using on their button animation. We might like to use the same opacity adjustment with our custom controls.</p>
<p>The starting shade of blue is #3478F6. We need to convert that to RGB, which gives us (52, 120, 246). The pressed state is #CDE0FA, otherwise known as (205, 224, 250). The background gray is #FBFBFC or (251, 251, 252). Please note that I&#8217;m getting these color values from taking a screenshot and picking off the hex values in Inkscape. They might not be perfect, but should be extremely close. Let’s go to the formula. We should expect opacity to equal the same thing whether we perform the calculation with the red, green, or blue values. We’ll use the red channel.</p>
<pre><code>205 = opacity * 52 + (1 - opacity) * 251

opacity = 23%
</code></pre>
<p>After a little algebra, opacity = 23%. As a test, what happens if we do the same thing with the green channel?</p>
<pre><code>224 = opacity * 120 + (1 - opacity) * 251

opacity = 21%
</code></pre>
<p>Simplified, opacity = 21%. That’s close. Let’s try the blue channel.</p>
<pre><code>250 = opacity * 246 + (1 - opacity) * 252

opacity = 33%
</code></pre>
<p>That’s puts opacity at 33%! So much for all of these opacity values being the same! Here&#8217;s the deal, though. RGB values are all integers, while opacity is a decimal. Inevitably, this will create some rounding error. This is especially true with the blue channel because all of the parameters in the equation are so close together. For instance, you could split the difference between the red and green channel calculations and assume an opacity of 22%. Let&#8217;s plug that in the blue equation to calculate an adjusted value.</p>
<pre><code>.22 * 246 + (1 - .22) * 252 =  250.68
</code></pre>
<p>You can see this is within a fraction of 250, the correct value. I suppose that makes 22% a fine choice for an opacity change on your button animations!</p>
<h2>Conclusion</h2>
<p>Opacity is a great tool to have at your disposal. We&#8217;ve talked about using it in the context of button transitions, but the possibilities are endless. You&#8217;ll often adjust opacity to give disabled controls a more transparent appearance, for example. In the event that you find a troublesome control without an opacity property, now you&#8217;ll be prepared.</p>
<p>Thanks for reading the HangZone blog, and have a great 2020!</p>
<p>The post <a href="https://hangzone.com/calculate-implied-rgb-color-opacity-changes/">How to Calculate the Implied RGB Color for Opacity Changes</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Custom Renderers in Xamarin.Forms Part 2</title>
		<link>https://hangzone.com/custom-renderers-xamarin-forms-part-2/</link>
		
		<dc:creator><![CDATA[Judson Bandy]]></dc:creator>
		<pubDate>Wed, 27 Nov 2019 18:25:13 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=2056</guid>

					<description><![CDATA[<p>In part 2 of our custom renderer blog post, we will continue to look at uses for custom renderers in Xamarin.Forms. In part 1, we made a navigation page in ...</p>
<p>The post <a href="https://hangzone.com/custom-renderers-xamarin-forms-part-2/">Custom Renderers in Xamarin.Forms Part 2</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>In part 2 of our custom renderer blog post, we will continue to look at uses for custom renderers in Xamarin.Forms. In part 1, we made a navigation page in Xamarin.Forms. The toolbar item on the page used an icon from <a href="http://material.io">material.io</a> for the Android version and used a system icon for the iOS version. We were able to use the system icon thanks to a custom renderer. If you have not read it, you can find part 1 <a href="https://hangzone.com/custom-renderers-xamarin-forms-part-1/">here</a>. We will start this project where part 1 left off.</p>
<h2>Extending an Element</h2>
<p>Let’s continue with the same Xamarin.Forms solution, which I named CustomRendererSample. The next thing that we want to add is a gradient underneath the toolbar. A shadow can be added to the Android project by adding an elevation property definition under the Resources-&gt;layout-&gt;Toolbar.axml file. This is not what we’re after though. We are going to add a loud, slightly obnoxious gradient on both iOS and Android under the toolbar.<span class="Apple-converted-space"> </span></p>
<p>The Xamarin.Forms control that we want to use is the BoxView. Unfortunately, we only have the ability to give it a single uniform color. To add our desired functionality, we will subclass BoxView and add additional properties to our new control. Then, we will create custom renderers for iOS and Android that will tell the system how to use our new properties. Hopefully, it won’t be too hard.</p>
<h2>Create our Custom BoxView</h2>
<p>Let’s start by creating our new BoxView class. Right click on the Shared Project and select Add-&gt;New Item… Add a new class called GradientBoxView.cs. We could create our properties simply as public properties, but instead, we are going to use BindableProperty. This will allow us to set these properties in a style. Styles do not make much sense if you are only using them once, but they can be quite helpful for maintaining and updating your code if objects with the same appearance are used multiple times.</p>
<p>Add the following code to your GradientBoxView class.</p>
<pre><code>public class GradientBoxView : BoxView
{
    public static readonly BindableProperty StartColorProperty = BindableProperty.Create("StartColor", typeof(Color), typeof(GradientBoxView), Color.Transparent);
    public static readonly BindableProperty EndColorProperty = BindableProperty.Create("EndColor", typeof(Color), typeof(GradientBoxView), Color.Transparent);
    public static readonly BindableProperty IsVerticalProperty = BindableProperty.Create("IsVertical", typeof(bool), typeof(GradientBoxView), true);

    public Color StartColor
    {
        get { return (Color)GetValue(StartColorProperty); }
        set { SetValue(StartColorProperty, value); }
    }

    public Color EndColor
    {
        get { return (Color)GetValue(EndColorProperty); }
        set { SetValue(EndColorProperty, value); }
    }

    public bool IsVertical
    {
        get { return (Color)GetValue(IsVerticalProperty); }
        set { SetValue(IsVerticalProperty, value); }
    }
}</code></pre>
<p>You can see that the class inherits from BoxView, so that we retain all of the functionality of that class. Then, we added variables for StartColor, EndColor, and IsVertical. As the names suggest, these will define the starting color, ending color, and direction of the gradient.</p>
<h2>Implementing the GradientBoxView</h2>
<p>Now, let’s head over to App.xaml to create a style. Like we said, a style is nice in case we want to use the same gradient on a number of pages. In the Application.Resources section, add the following code.<span class="Apple-converted-space"> </span></p>
<pre><code>&lt;ResourceDictionary&gt;
    &lt;Style x:Key=“AwesomeGradientStyle” TargetType=“customrenderersample:GradientBoxView”&gt;
        &lt;Setter Property=“StartColor” Value=“DarkBlue”/&gt;
        &lt;Setter Property=“EndColor” Value=“#10FFFFFF”/&gt;
        &lt;Setter Property=“IsVertical” Value=“True”/&gt;
        &lt;Setter Property=“HeightRequest” Value=“10”/&gt;
    &lt;/Style&gt;
&lt;/ResourceDictionary&gt;</code></pre>
<p>As you can see, a style is added that sets all of our properties for the GradientBoxView. App.xaml will have to add “xmlns:customrendersample=“clr-namespace:CustomRendererSample” so that GradientBoxView will be imported into this class.</p>
<p>Now, we can jump over to MainPage.xaml to add GradientBoxView to the page. Change the ContentPage.Content to look like this.</p>
<pre><code>&lt;ContentPage.Content&gt;
    &lt;Grid&gt;
        &lt;Grid.RowDefinitions&gt;
            &lt;RowDefinition Height="auto"/&gt;
            &lt;RowDefinition Height="*"/&gt;
        &lt;/Grid.RowDefinitions&gt;
        &lt;customrenderersample:GradientBoxView Style=“{StaticResource AwesomeGradientStyle}”/&gt;
        &lt;Label Grid.Row="1" x:Name="SearchLabel" Text="Search" VerticalOptions="Center" HorizontalOptions="Center"/&gt;
    &lt;/Grid&gt;
&lt;/ContentPage.Content&gt;</code></pre>
<p>You can see that we adjusted our grid to put the gradient underneath the toolbar at the top of the page, and our SearchLabel is still in the middle. Again, we need the same CustomRendererSample definition and prefix to use the class. That wraps up the code in our Shared Project. Let’s move on to our custom renderers and set our new GradientBoxView properties in motion.</p>
<h2>iOS Custom Renderer</h2>
<p>Let’s start with the iOS renderer. Its code is a little simpler, so it should be an easier place to begin. Navigate to the iOS project, right click and select Add-&gt;New Item… Choose a class and name it GradientBoxViewRenderer.cs. Click the Add button, and we’re ready to get started.</p>
<p>First, we need to add an assembly line so that this code will be used. Above the line that defines the namespace, add:</p>
<pre><code>[assembly: ExportRenderer(typeof(GradientBoxView), typeof(GradientBoxViewRenderer))]</code></pre>
<p>This line tells our code to use this renderer whenever GradientBoxView occurs in the code. If you get errors in this code, or anywhere else in the custom renderers, you should be able to hover over the error and select for Visual Studio to add the appropriate using directive. Next, we will make this class a subclass of BoxRenderer.</p>
<pre><code>class GradientBoxViewRenderer : <b>BoxRenderer</b></code></pre>
<p>To add our gradient to the boxview, we only need to override one method, the Draw function.</p>
<pre><code>public override void Draw(CGRect rect)
{
    base.Draw(rect);

    var stack = Element as GradientBoxView;
    var topColor = stack.StartColor.ToCGColor();
    var bottomColor = stack.EndColor.ToCGColor();
    var gradientLayer = new CAGradientLayer
    {
        Frame = rect,
        Colors = new CGColor[] { topColor, bottomColor }
    };

    if (!stack.IsVertical)
    {
        gradientLayer.StartPoint = new CGPoint(0, 0.5);
        gradientLayer.EndPoint = new CGPoint(1, 0.5);
    }

    NativeView.Layer.InsertSublayer(gradientLayer, 0);
}</code></pre>
<p>Let’s walk through what is going on. First, we are getting our GradientBoxView and setting it to a local variable, stack. Next, we are getting the start color and end color from it, and converting the colors to CGColors. Then, we create a CAGradientLayer to the size of our BoxView with the gradient colors. Finally, we adjust the direction if horizontal is desired, and we add the gradient to the native iOS BoxView. That’s it! It is fairly straightforward, and just like that, the iOS version is good to go. Now, let’s move to Android.</p>
<h2>Android Custom Renderer</h2>
<p>This custom renderer is a little more involved, but we can do it. Go to the Android project in the Solution Explorer, and add a new class just like we did for the iOS renderer. We will also name this class GrandientBoxViewRenderer.cs. Add the same assembly directive above the namespace definition so that this renderer will be used for all GradientBoxViews when running on Android. Then, add the code so that this class will be a subclass of BoxRenderer. That’s where the same code ends.</p>
<p>First, we’ll add instance variables for our GradientBoxView properties at the top of the renderer class definition and an empty initializer for the class.</p>
<pre><code>public class GradientBoxViewRenderer : BoxRenderer
{
    <b>private Color StartColor { get; set; }
    private Color EndColor { get; set; }
    private bool IsVertical { get; set; }

    public GradientBoxViewRenderer(Context context) : base(context)
    {

    }</b>
}</code></pre>
<p>Then, we will override OnElementChanged. We will use this to set the values of our instance variables.</p>
<pre><code>protected override void OnElementChanged(ElementChangedEventArgs&lt;BoxView&gt; e)
{
    base.OnElementChanged(e);

    if (e.OldElement != null || Element == null)
    {
        return;
    }

    try
    {
        var stack = e.NewElement as GradientBoxView;

        this.StartColor = stack.StartColor;
        this.EndColor = stack.EndColor;
        this.IsVertical = stack.IsVertical;
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(@"ERROR:", ex.Message);
    }
}</code></pre>
<p>Now that we have our variables from GradientBoxView, it’s time to do our drawing code to create the gradient on the Android BoxView.</p>
<pre><code>protected override void DispatchDraw(global::Android.Graphics.Canvas canvas)
{
    if (global::Android.OS.Build.VERSION.SdkInt &gt; global::Android.OS.BuildVersionCodes.O)
    {
        var orientation = GradientDrawable.Orientation.TopBottom;

        if (!this.IsVertical)
        {
            orientation = GradientDrawable.Orientation.LeftRight;
        }

        var specialGradient = new GradientDrawable(orientation, new[] { this.StartColor.ToAndroid().ToArgb(), this.EndColor.ToAndroid().ToArgb() });
        ViewCompat.SetBackground(this, specialGradient);
        return;
    }

    global::Android.Graphics.LinearGradient gradient = null;

    if (this.IsVertical)
    {
        gradient = new global::Android.Graphics.LinearGradient(0, 0, 0, Height, this.StartColor.ToAndroid(),this.EndColor.ToAndroid(), global::Android.Graphics.Shader.TileMode.Clamp);
    }
    else
    {
        gradient = new global::Android.Graphics.LinearGradient(0, 0, Width, 0, this.StartColor.ToAndroid(), this.EndColor.ToAndroid(), global::Android.Graphics.Shader.TileMode.Clamp);
    }

    var paint = new global::Android.Graphics.Paint()
    {
        Dither = true,
    };
    paint.SetShader(gradient);
    canvas.DrawPaint(paint);
    base.DispatchDraw(canvas);
}</code></pre>
<p>As you can see, the technique is different for Android versions greater than O. For the newer versions, we simply get our orientation, then create a GradientDrawable with our colors. Then, we set it as the background on the native ViewCompat. For the older Android versions, we create a LinearGradient based on the gradient direction. Then, we DrawPaint the gradient onto the canvas. That’s it for Android and our GradientBoxView. Run the project and see your custom GradientBoxView in action! You can easily change the color, direction, or size if you so desire.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2019/11/Blog-Xamarin-Forms-Custom-Renderer-Finished-App.png"><img decoding="async" class="alignnone size-medium wp-image-2057" src="https://hangzone.com/wp-content/uploads/2019/11/Blog-Xamarin-Forms-Custom-Renderer-Finished-App-188x300.png" alt="Blog Xamarin Forms Custom Renderer Finished App" width="188" height="300" srcset="https://hangzone.com/wp-content/uploads/2019/11/Blog-Xamarin-Forms-Custom-Renderer-Finished-App-188x300.png 188w, https://hangzone.com/wp-content/uploads/2019/11/Blog-Xamarin-Forms-Custom-Renderer-Finished-App-640x1024.png 640w, https://hangzone.com/wp-content/uploads/2019/11/Blog-Xamarin-Forms-Custom-Renderer-Finished-App.png 674w" sizes="(max-width: 188px) 100vw, 188px" /></a></p>
<h2>Conclusion</h2>
<p>Great job! We successfully created a custom control in Xamarin.Forms—a GradientBoxView. This is a nice tool for adding gradients under objects in your projects to further set them off or provide separation of sections. Not only that, but you should now have a good idea of how to create other custom controls using custom renderers. It’s not always simple, but you can usually use custom renderers to accomplish any visual look or action that would natively be available on the platform. As always, thanks for reading the HangZone blog!</p>
<p>The post <a href="https://hangzone.com/custom-renderers-xamarin-forms-part-2/">Custom Renderers in Xamarin.Forms Part 2</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Free Space in Xcode and Android Studio</title>
		<link>https://hangzone.com/free-space-xcode-android-studio/</link>
		
		<dc:creator><![CDATA[Tyler Bandy]]></dc:creator>
		<pubDate>Thu, 31 Oct 2019 19:58:10 +0000</pubDate>
				<category><![CDATA[Developer Insights]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=2051</guid>

					<description><![CDATA[<p>My 5 year old MacBook recently started complaining about not having enough space. I don’t keep any music on my computer, and I have relatively few pictures. If I go ...</p>
<p>The post <a href="https://hangzone.com/free-space-xcode-android-studio/">How to Free Space in Xcode and Android Studio</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>My 5 year old MacBook recently started complaining about not having enough space. I don’t keep any music on my computer, and I have relatively few pictures. If I go to the About This Mac menu, and click on the Storage tab, the primary culprit is the System type. It takes up over 300 GB of my 500GB hard drive!</p>
<p>If you’ve been developing mobile apps for a few years on the same computer, I suspect you also have a glut of these opaque system files on your machine. Unlike pictures and music, it’s not so obvious how to clean them up. First, we have to identify what these files are, and then we need to figure out a safe way to dispose of them. Be advised that these files reside in the infamous Library folder. Apple hid it for a reason, so be careful!</p>
<h2>Navigating the Library Folder</h2>
<p>If you’re a developer, you’re probably accustomed to opening the Library folder every once in a while. Just in case you’ve forgotten how to get there, open up a Finder window, hold down the option key, and click on the Go menu at the top. Holding down the option key reveals the Library folder. Go ahead and click it.</p>
<p>From here, you can right click any of the folders inside and click Get Info. That will show you how much space each item takes up. As a mobile developer, Xcode and Android Studio are going to be the primary targets for the purge. Feel free to look around and see if you have any other massive file hoards, but we’ll be focusing on the Android and Developer folders.</p>
<h2>Xcode Developers</h2>
<p>Let’s start by dealing with our build-up of Xcode files. Navigate to Library/Developer/Xcode/iOS DeviceSupport. This folder is about 95 GB for me, and it contains symbol files dating back to iOS 6! This is more than a little unnecessary. Keep the folders for the iOS versions you still care about. You can safely delete everything in the folder for the other iOS versions. Even if you accidentally delete the support files for your favorite development device, Xcode will download the appropriate symbol files for that device again the next time you plug it in.</p>
<p>The other major storage hog in the Xcode environment is all of the old simulator data. Go to Library/Developer/CoreSimulator/Devices. I’ve got hundreds of old simulators in here, and they take up about 35 GB of space combined. To make things more complicated, their names are all lengthy strings of seemingly random letters and numbers. I have no easy way of knowing which ones are new and which ones are old.</p>
<p>Fortunately, Xcode tracks which simulators are no longer in use for your current Xcode version. Even better, we can purge them all it once through the Terminal. Open up Terminal and enter the command.</p>
<pre><code>xcrun simctl delete unavailable</code></pre>
<p>You’ll find a much shorter list of simulators in the Devices folder now. Mine take up less than 500 MB now! Between these two maneuvers, I’ve saved over 100 GB of space!</p>
<p>If you were to open Xcode now, you might find some problems. My storyboards turned black, and Xcode showed a vague error. We’re going to remedy this problem by deleting more files! Head over to Library/Developer/Xcode/DerivedData. This is a cache of files created when you build Xcode projects. You can safely delete everything in here. If you do happen to still get an Xcode error, clean your project, close Xcode, delete the DerivedData again, and restart your computer. You should be good as new!</p>
<p>There are other space saving options for Xcode, but they’re less meaningful and some are potentially more dangerous. For instance, you could delete some archives, but you might want them for debugging. I’m going to stop here for now, and call this a win on the Xcode front.</p>
<h2>Android Developers</h2>
<p>Space management on Android is a little more transparent than on Xcode. Considering you manually download which SDKs you want and create individual emulators for testing, you probably have a good idea of what’s taking up space here. Even so, sometimes it’s hard to know exactly how big an SDK or simulator actually is without looking it up. We’ll do that now.</p>
<p>Go to Library/Android/sdk/system-images. This has all of the Android SDKs that you’ve downloaded. Check the size on them. Mine are anywhere from 6 GB to 25 GB. Rather than deleting directly from here, I’ll use Android Studio. Click Configure from the opening dialog, and select SDK Manager. Delete SDKs that you don’t use for testing anymore, particularly if they’re exceptionally large.</p>
<p>Lastly, emulators aren’t going to take up as much space, but head over to the AVD Manager under the Tools menu. Delete the old devices you don’t use, and you’ll be awarded with a few free GB for your effort.</p>
<h2>Conclusion</h2>
<p>I’ve always enjoyed how Xcode downloads everything I need automatically compared to Android Studio forcing me to decide what I need. Of course, the downside is that Xcode will eventually take up a ton of space before you realize it. No worries! You can clean-up everything in a couple of minutes. If you waited as long as I did, you too may have 150 GB of new free space! So much room for activities!</p>
<p>The post <a href="https://hangzone.com/free-space-xcode-android-studio/">How to Free Space in Xcode and Android Studio</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Custom Renderers in Xamarin.Forms Part 1</title>
		<link>https://hangzone.com/custom-renderers-xamarin-forms-part-1/</link>
		
		<dc:creator><![CDATA[Judson Bandy]]></dc:creator>
		<pubDate>Mon, 30 Sep 2019 16:06:38 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=2041</guid>

					<description><![CDATA[<p>Xamarin.Forms is a great tool for cross platform app development. It gives you the ability to write shared code that is used by different operating systems. Not only are you ...</p>
<p>The post <a href="https://hangzone.com/custom-renderers-xamarin-forms-part-1/">Custom Renderers in Xamarin.Forms Part 1</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Xamarin.Forms is a great tool for cross platform app development. It gives you the ability to write shared code that is used by different operating systems. Not only are you able to write shared logic, but you are also able to write shared UI code. This allows you to save time in initial development and makes it much more maintainable since there is only one version of the code. Not only this, but Xamarin.Forms is able to use native elements of each platform so that you are still able to get a native look and feel for each device.</p>
<p>Although this shared code is fast and works well, it doesn’t always provide the granularity that we are looking to use for each platform. Sometimes a finer brush is needed to paint our masterpiece. This is no reason to fret, though. Xamarin.Forms allows us to create custom renderers so that we are able to extend controls to take advantage of native specific elements for each platform. In this blog post, we will create a couple of custom renderers in a Xamarin.Forms project to make elements that are more robust than what comes out of the box.</p>
<h2>Create Xamarin.Forms Project</h2>
<p>Let’s start by creating a new Xamarin.Forms project. I am developing on a Windows computer in Visual Studio 2019. You can do this just as well on Visual Studio for Mac. We are are going to write this app to run for both iOS and Android.</p>
<p>First, open Visual Studio, and create a new project. Select Mobile App (Xamarin.Forms), and click Next. Give your project a name, mine is called “CustomRendererSample,” choose a location, and create the project. Select a Blank template, and for platforms, we are doing Android and iOS. You can certainly do UWP as well, but we are only targeting iOS and Android in this example. Press OK, and our project is now created.</p>
<h2>Toolbar Buttons</h2>
<p>For this example, we are going to make an app with a hierarchical navigation scheme. This has the toolbar at the top, and pages are added to and popped off the stack as you navigate. We are only going to have one page for our example, though. With the toolbar at the top, it is common to have toolbar buttons. These can be save, cancel, back, refresh, and so on. You will find these in many of the first party phone apps (like Settings) or pretty much any native enterprise style app. Toolbar buttons can be created through Xamarin.Forms, but as an extension to the control, we are going to have the app use System toolbar buttons on iOS to get a totally native look.</p>
<p>First, let’s get the navigation page set up for the app. Open App.xaml.cs in the Shared Project of your Solution. Make the following edit to the constructor.</p>
<pre><code>MainPage = <b>new NavigationPage(</b>new MainPage()<b>)</b>;</code></pre>
<p>This creates a NavigationPage as the root page of the app, and then adds MainPage as the content of the navigation page. Feel free to go ahead and run the app. You should now see a blank toolbar at the top of your screen, and the Xamarin.Forms welcome label displayed in the middle of it. We’re off to a good start!</p>
<p>Now, open MainPage.xaml. Start by deleting the entire StackLayout and the welcome label that is inside it. We are going to make our own content later.</p>
<pre><code><del>&lt;StackLayout&gt;
    &lt;!-- Place new controls here --&gt;
    &lt;Label Text="Welcome to Xamarin.Forms!"
      HorizontalOptions="Center"
      VerticalOptions="CenterAndExpand" /&gt;
&lt;/StackLayout&gt;</del></code></pre>
<p>Next, we’ll give the page a title to go on the toolbar. Set the title property on ContentPage to “Awesome Page.”</p>
<pre><code>&lt;ContentPage …… <b>Title=“Awesome Page”</b>&gt;</code></pre>
<h2>Android Toolbar Buttons</h2>
<p>We don’t have to add images to our project for iOS since we are using system buttons, but we do need them for Android. We are able to download Android buttons without any problem from the material.io website. All Android icons are open source and are available to be used in any project. We are going to use the search icon in our project. Go to this <a href="https://material.io/resources/icons/?style=baseline">page</a>, select the “search” icon, and download the SVG 24 file.<span class="Apple-converted-space"> </span></p>
<p>I like to open this file in Inkscape (use can use any vector drawing program to edit the file without losing image quality). The image, which is a little larger than 17&#215;17, is designed to be centered on a transparent 24&#215;24 background. Once you do that, we want to change the color of the icon to white, and create 3 different sizes—24&#215;24, 48&#215;48, and 72&#215;72. Now, they are ready to export and add to our project. Please see the image below for what my icons look like.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2019/09/Blog-Xamarin-Forms-Custom-Renderers-Toolbar-Search-Icons.png"><img decoding="async" class="alignnone size-medium wp-image-2045" src="https://hangzone.com/wp-content/uploads/2019/09/Blog-Xamarin-Forms-Custom-Renderers-Toolbar-Search-Icons-300x127.png" alt="Blog Xamarin Forms Custom Renderers Toolbar Search Icons" width="300" height="127" srcset="https://hangzone.com/wp-content/uploads/2019/09/Blog-Xamarin-Forms-Custom-Renderers-Toolbar-Search-Icons-300x127.png 300w, https://hangzone.com/wp-content/uploads/2019/09/Blog-Xamarin-Forms-Custom-Renderers-Toolbar-Search-Icons-1024x435.png 1024w, https://hangzone.com/wp-content/uploads/2019/09/Blog-Xamarin-Forms-Custom-Renderers-Toolbar-Search-Icons.png 1154w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<p>Let’s go back to Visual Studio. Inside the Android project, open the Resource folder. Drag the 24&#215;24, 48-48, and 72&#215;72 icons into the drawable, drawable-hdpi, and drawable-xxhdpi folders, respectively. All three of my icons are named “searchIconDroid.png.” Now that the icons are created and added to the project, we can return to the code.</p>
<p>On a side note, these drawable folders are currently not working in the Microsoft version of Visual Studio. You can look through the File Explorer and see that the folders do not actually exist. To fix it, simply delete the fake folders that are currently shown, and re-add folders with the same names.</p>
<h2>Create the Toolbar Item in XAML</h2>
<p>Open MainPage.xaml again, and add the following code.</p>
<pre><code>&lt;ContentPage.ToolbarItems&gt;
    &lt;ToolbarItem x:Name="SearchButton"
      Text="Search"&gt;
        &lt;ToolbarItem.IconImageSource&gt;
            &lt;OnPlatform x:TypeArguments="FileImageSource"&gt;
                &lt;On Platform="iOS" Value=""/&gt;
                &lt;On Platform="Android" Value="searchIconDroid"/&gt;
            &lt;/OnPlatform&gt;
        &lt;/ToolbarItem.IconImageSource&gt;
    &lt;/ToolbarItem&gt;
&lt;/ContentPage.ToolbarItems&gt;</code></pre>
<p>This code creates a ToolbarItem named “SearchButton.” We aren’t going to reference the button in this project, but it can still be helpful to give a name to your elements. We set the “Text” to “Search.” This is what our iOS custom renderer is going to use to identify what kind of system bar button should be used. Finally, we set the IconImageSource. We use platform specific code where the Android version is set to the name of the search icon that we created, and the iOS version is left empty. You can run this code on an Android device and see our beautiful search icon, but the iOS version still needs a custom renderer.</p>
<h2>iOS Toolbar Button Custom Renderer</h2>
<p>It’s finally time to create our first custom renderer. Right click on the iOS Project in your Solution and select Add-&gt;New Item… Add a new class called CustomNavigationRenderer.cs. To set up the page, we have to add an assembly directive to the top. This line makes all instances of the Xamarin control referenced flow through this custom renderer. Add the following line (note: many of the lines will require additional using namespaces to be added. Visual Studio should show errors and tell you which namespaces need to be added).</p>
<pre><code>[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomNavigationRenderer))]</code></pre>
<p>You can see that this names ContentPage, which is the page type that we are using in the Shared Project, and the name of this renderer. Next, add that our new renderer class inherits from PageRenderer.</p>
<pre><code>class CustomNavigationRenderer : <b>PageRenderer</b></code></pre>
<p>Now we add the logic to the page.</p>
<pre><code>protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
    base.OnElementChanged(e);
    CreateNativeToolbarItems();
}

private void CreateNativeToolbarItems()
{
    if (this.NavigationController == null)
        return;
    if (this.NavigationController.TopViewController == null)
        return;
    if (this.NavigationController.TopViewController.NavigationItem == null)
        return;
    if (this.NavigationController.TopViewController.NavigationItem.RightBarButtonItems == null)
        return;

    var rightList = new List&lt;UIBarButtonItem&gt;();
    foreach (var item in this.NavigationController.TopViewController.NavigationItem.RightBarButtonItems)
    {
        if (string.IsNullOrEmpty(item.Title))
        {
            continue;
        }
        if (item.Title.ToLower() == "search")
        {
            var newItem = new UIBarButtonItem(UIBarButtonSystemItem.Search)
            {
                Action = item.Action,
                Target = item.Target
            };
            rightList.Add(newItem);
        }
        else
        {
            rightList.Add(item);
        }
    }
    if (rightList.Count &gt; 0)
    {
        this.NavigationController.TopViewController.NavigationItem.RightBarButtonItems = rightList.ToArray();
    }
}</code></pre>
<p>We override the OnElementChanged method, which is defined in the parent class. This is called when the ContentPage is created or changed. We use this method to call our CreateNativeToolbarItems method. After checking that the NavigationController and toolbar pieces exist, we cycle through the toolbar buttons that have been created. If the title is set to “search,” which we did in the xaml code by setting the text property, we create a new UIBarButtonItem. This bar button is the system search icon and we put it into our RightBarButtonItems array instead of the button that was created by the XAML.</p>
<p>Run the project on iOS and you will find a system search icon nestled on the right side of the toolbar. Good job!</p>
<h2>Toolbar Action</h2>
<p>It’s not necessary, but we might as well have something happen when we press the toolbar button. Go back to MainPage.xaml, and add the following property to our ToolbarItem.</p>
<pre><code>Clicked=“SearchButton_Clicked”</code></pre>
<p>We will also add a label to be manipulated when the button is pressed. Below the ToolbarItems code, add the following.</p>
<pre><code>&lt;ContentPage.Content&gt;
    &lt;Grid&gt;
        &lt;Label x:Name”SearchLabel” Text=“Search” VerticalOptions=“Center” HorizontalOptions=“Center”/&gt;
    &lt;/Grid&gt;
&lt;/ContentPage.Content&gt;</code></pre>
<p>Now, let’s go to the MainPage.xaml.cs file to define the click method. Add the following.</p>
<pre><code>private void SearchButton_Clicked(object sender, EventArgs e)
{
    if (SearchLabel.TextColor == Color.Black)
    {
        SearchLabel.TextColor = Color.Green;
    }
    else
    {
        SearchLabel.TextColor = Color.Black;
    }
}</code></pre>
<p>This code will change the color of the label on the middle of the screen each time you click the search icon. Run the code and watch it in action. The label changes to black, then green, then black again on subsequent clicks.</p>
<h2>Conclusion</h2>
<p>Great job! In this tutorial, we put a toolbar item on a page in Xamarin.Forms. For Android, we used icons from <a href="http://material.io">material.io</a>, sized them properly, and added them to our project. For iOS, we used a custom renderer to display system icons in the project. Even though we are using Xamarin.Forms, we were able to display a completely native look on each device. Feel free to further extend our iOS custom renderer to have left side toolbar buttons, different colored toolbar buttons, or anything else you can think of. As always, thanks for reading the HangZone blog.</p>
<p>The post <a href="https://hangzone.com/custom-renderers-xamarin-forms-part-1/">Custom Renderers in Xamarin.Forms Part 1</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Fetch Stock and Currency Quotes for iPhone</title>
		<link>https://hangzone.com/how-to-fetch-stock-and-currency-quotes-for-iphone/</link>
		
		<dc:creator><![CDATA[Tyler Bandy]]></dc:creator>
		<pubDate>Fri, 30 Aug 2019 21:01:41 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=2027</guid>

					<description><![CDATA[<p>When I’m not writing apps, I like to follow stocks. I’ve even had the opportunity to combine those interests in the past with quantitative trading strategies. We’ll keep things simple ...</p>
<p>The post <a href="https://hangzone.com/how-to-fetch-stock-and-currency-quotes-for-iphone/">How to Fetch Stock and Currency Quotes for iPhone</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>When I’m not writing apps, I like to follow stocks. I’ve even had the opportunity to combine those interests in the past with quantitative trading strategies. We’ll keep things simple today and just query stock and currency quotes from our iPhone.</p>
<h2>Get an Alpha Vantage API Key</h2>
<p>There are a lot of financial datasources, and many of them cost a good bit of money. That’s no surprise considering the massive budgets successful hedge funds have to spend on these products. Yahoo Finance was traditionally the most popular free source for stock price data, but that’s no longer an option. Fortunately, other free alternatives have emerged to fill the void.</p>
<p>Today, we’ll use Alpha Vantage. They have realtime and historical data for stocks and currencies, including cryptos. The assortment of technical indicator data is also quite robust. <a href="https://www.alphavantage.co">Head on over there and get a free API key</a>, and let’s get started.</p>
<h2>Create an Xcode Project</h2>
<p>Open up Xcode and create a new tabbed app. Call the app <em>Quotes</em>, and set the language as Swift. Open up <em>Main.Storyboard</em>. You should see a tab bar controller and two view controllers. We’ll use one view controller for stocks and the other for currency.</p>
<h2>Stock Storyboard</h2>
<p>The first controller will be for getting stock quotes. In fact, we actually want more than just the current price of the stock. We’d like all sorts of valuable peripherals. You can see I’ve added quite a few labels on my storyboard to cover all the data.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2019/08/Quotes-Storyboard-Stocks.png"><img decoding="async" class="alignnone wp-image-2032 size-medium" src="https://hangzone.com/wp-content/uploads/2019/08/Quotes-Storyboard-Stocks-137x300.png" alt="Quotes Storyboard Stocks" width="137" height="300" srcset="https://hangzone.com/wp-content/uploads/2019/08/Quotes-Storyboard-Stocks-137x300.png 137w, https://hangzone.com/wp-content/uploads/2019/08/Quotes-Storyboard-Stocks.png 388w" sizes="(max-width: 137px) 100vw, 137px" /></a></p>
<p>To recap, the fields are Symbol, Open, High, Low, Price, Volume, Latest Trading Day, Previous Close, Change, and Change Percent. With that in mind, go ahead and drag 20 labels onto your storyboard, positioned in 2 columns of 10. The left column has the field names, and the right column has the values. You won’t actually see those filler names for the values in the app. That’s just for readability in the storyboard. We’ll clear them in the code. Now embed each column of labels into a stack view. Then you can use constraints to get the left stack view away from the top and left edges of the device. Center the right stack view vertically with the left one, and use a standard amount of leading space.</p>
<p>At the bottom of the view controller, I added an instructional label (&#8220;Enter Stock Ticker&#8221;), a text field, and a search button. They’re all centered horizontally with about 40 vertical spacing between each object. Remember to set your text field to capitalize all characters and not autocorrect anything. That&#8217;s important for dealing with stock tickers. You can set those properties in the attributes inspector.</p>
<p>Once you’ve finished with the objects and constraints, tap the tab bar item at the bottom of the view controller, and go to the attributes inspector. Change its title to &#8220;Stocks&#8221;. That takes care of this view controller on the storyboard!</p>
<h2>Currency Storyboard</h2>
<p>The currency view controller is laid out basically the same way, only it has fewer fields. We only need pairs of labels for the following: Currency Code, Currency Name, Exchange Rate, Last Refreshed, Bid, and Ask. Here’s a picture for reference.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2019/08/Quotes-Storyboard-Currency.png"><img decoding="async" class="alignnone size-medium wp-image-2031" src="https://hangzone.com/wp-content/uploads/2019/08/Quotes-Storyboard-Currency-138x300.png" alt="Quotes Storyboard Currency" width="138" height="300" srcset="https://hangzone.com/wp-content/uploads/2019/08/Quotes-Storyboard-Currency-138x300.png 138w, https://hangzone.com/wp-content/uploads/2019/08/Quotes-Storyboard-Currency.png 390w" sizes="(max-width: 138px) 100vw, 138px" /></a></p>
<p>The instructional label should say &#8220;Enter Currency Code&#8221;. The tab bar title will say &#8220;Currency&#8221;.</p>
<h2>Linking Storyboard</h2>
<p>There are quite a few labels to link from out storyboard to the code. Let’s get started. Hook-up the following IBOutlets using the value labels in our stock view controller. These will all link to the code in <em>FirstViewController</em>. Please note that these are from the value column on the right. We don’t need to hook-up the labels on the left.</p>
<pre><code>@IBOutlet weak var stockSymbolLabel: UILabel
@IBOutlet weak var stockOpenLabel: UILabel!
@IBOutlet weak var stockHighLabel: UILabel!
@IBOutlet weak var stockLowLabel: UILabel!
@IBOutlet weak var stockPriceLabel: UILabel!
@IBOutlet weak var stockVolumeLabel: UILabel!
@IBOutlet weak var stockLastTradingDayLabel: UILabel!
@IBOutlet weak var stockPreviousCloseLabel: UILabel!
@IBOutlet weak var stockChangeLabel: UILabel!
@IBOutlet weak var stockChangePercentLabel: UILabel!
</code></pre>
<p>While we’re here, let’s do the text field as well.</p>
<pre><code>@IBOutlet weak var stockTextField: UITextField!
</code></pre>
<p>We need to link the button too, but this one is an IBAction.</p>
<pre><code>@IBAction func stockSearchTapped(_ sender: Any) {

}
</code></pre>
<p>The pattern is similar for the currency view controller. These storyboard objects will link with <em>SecondViewController</em>.</p>
<pre><code>@IBOutlet weak var currencyCodeLabel: UILabel!
@IBOutlet weak var currencyNameLabel: UILabel!
@IBOutlet weak var currencyExchangeRateLabel: UILabel!
@IBOutlet weak var currencyLastRefreshedLabel: UILabel!
@IBOutlet weak var currencyBidValue: UILabel!
@IBOutlet weak var currencyAskValue: UILabel!
@IBOutlet weak var currencyTextField: UITextField!
@IBAction func currencySearchButtonTapped(_ sender: Any) {

}
</code></pre>
<h2>Query a Stock Quote</h2>
<p>In <em>FirstViewController</em>, we’ll add a function to query the stock data.</p>
<pre><code>func getStockQuote() {
    let session = URLSession.shared
    
    let quoteURL = URL(string: "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&amp;symbol=\(stockTextField.text ?? "")&amp;apikey=APIKEY")!
    
    let dataTask = session.dataTask(with: quoteURL) {
        (data: Data?, response: URLResponse?, error: Error?) in
        
        if let error = error {
            print("Error:\n\(error)")
        } else {
            if let data = data {
                
                let dataString = String(data: data, encoding: String.Encoding.utf8)
                print("All the quote data:\n\(dataString!)")
                    
                if let jsonObj = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary {
                        
                    if let quoteDictionary = jsonObj.value(forKey: "Global Quote") as? NSDictionary {
                        DispatchQueue.main.async {
                            if let symbol = quoteDictionary.value(forKey: "01. symbol") {
                                self.stockSymbolLabel.text = symbol as? String
                            }
                            if let open = quoteDictionary.value(forKey: "02. open") {
                                self.stockOpenLabel.text = open as? String
                            }
                            if let high = quoteDictionary.value(forKey: "03. high") {
                                self.stockHighLabel.text = high as? String
                            }
                            if let low = quoteDictionary.value(forKey: "04. low") {
                                self.stockLowLabel.text = low as? String
                            }
                            if let price = quoteDictionary.value(forKey: "05. price") {
                                self.stockPriceLabel.text = price as? String
                            }
                            if let volume = quoteDictionary.value(forKey: "06. volume") {
                                self.stockVolumeLabel.text = volume as? String
                            }
                            if let latest = quoteDictionary.value(forKey: "07. latest trading day") {
                                self.stockLastTradingDayLabel.text = latest as? String
                            }
                            if let previous = quoteDictionary.value(forKey: "08. previous close") {
                                self.stockPreviousCloseLabel.text = previous as? String
                            }
                            if let change = quoteDictionary.value(forKey: "09. change") {
                                self.stockChangeLabel.text = change as? String
                            }
                            if let changePercent = quoteDictionary.value(forKey: "10. change percent") {
                                self.stockChangePercentLabel.text = changePercent as? String
                            }
                        }
                    } else {
                        print("Error: unable to find quote")
                        DispatchQueue.main.async {
                            self.resetLabels()
                        }
                    }
                } else {
                    print("Error: unable to convert json data")
                    DispatchQueue.main.async {
                        self.resetLabels()
                    }
                }
            } else {
                print("Error: did not receive data")
                DispatchQueue.main.async {
                    self.resetLabels()
                }
            }
        }
    }
    
    dataTask.resume()
}
</code></pre>
<p>Most of this is UI code to update the labels, so don’t get too intimidated by the length. Let’s start at the top and make some sense of this. The URLSession and dataTask code launch the API call. Our API call is spelled out in quoteURL constant. There are 3 properties that we are passing to the server. The first is the function name, &#8220;GLOBAL_QUOTE&#8221;. This is Alpha Vantage’s function for getting stock quotes. The second is the symbol. We’re passing in whatever text the user has entered in the text field. The last parameter is the API key. In the code above, I set the API key equal to &#8220;APIKEY&#8221;. Put your own API key into your code.</p>
<p>All of this effort nets us a JSON package full of data for our requested stock ticker. We’ll convert that JSON into a dictionary. I’ve included a print to the console which helps you visualize the dictionary data. It’s basically a dictionary with one key, “Global Quote”. The value for that key is another dictionary, this time with keys “01. symbol”, “02. open”, “03. high”, “04. low”, etc. There’s one key for each piece of data we’d like. Our code simply checks each of these keys for a value and updates our labels. If there’s an error for whatever reason, most likely due to entering an invalid ticker, we log the error to the console and reset the labels. For a production app, you should show the user some sort of error, but this will be fine for a tutorial.</p>
<h2>Query a Currency Exchange Rate</h2>
<p>In <em>SecondViewController</em>, we need a similar function to query currency. Here it is.</p>
<pre><code>func getCurrencyQuote() {
    let session = URLSession.shared
    
    let quoteURL = URL(string: "https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&amp;from_currency=\(currencyTextField.text ?? "")&amp;to_currency=USD&amp;apikey=APIKEY")!
    
    let dataTask = session.dataTask(with: quoteURL) {
        (data: Data?, response: URLResponse?, error: Error?) in
        
        if let error = error {
            print("Error:\n\(error)")
        } else {
            if let data = data {
                
                let dataString = String(data: data, encoding: String.Encoding.utf8)
                print("All the quote data:\n\(dataString!)")
                
                if let jsonObj = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary {
                    
                    if let quoteDictionary = jsonObj.value(forKey: "Realtime Currency Exchange Rate") as? NSDictionary {
                        DispatchQueue.main.async {
                            if let code = quoteDictionary.value(forKey: "1. From_Currency Code") {
                                self.currencyCodeLabel.text = code as? String
                            }
                            if let name = quoteDictionary.value(forKey: "2. From_Currency Name") {
                                self.currencyNameLabel.text = name as? String
                            }
                            if let rate = quoteDictionary.value(forKey: "5. Exchange Rate") {
                                self.currencyExchangeRateLabel.text = "$\(rate)"
                            }
                            if let date = quoteDictionary.value(forKey: "6. Last Refreshed") {
                                self.currencyLastRefreshedLabel.text = date as? String
                            }
                            if let bid = quoteDictionary.value(forKey: "8. Bid Price") {
                                self.currencyBidValue.text = "$\(bid)"
                            }
                            if let ask = quoteDictionary.value(forKey: "9. Ask Price") {
                                self.currencyAskValue.text = "$\(ask)"
                            }
                        }
                    } else {
                        print("Error: unable to find quote")
                        DispatchQueue.main.async {
                            self.resetLabels()
                        }
                    }
                } else {
                    print("Error: unable to convert json data")
                    DispatchQueue.main.async {
                        self.resetLabels()
                    }
                }
            } else {
                print("Error: did not receive data")
                DispatchQueue.main.async {
                    self.resetLabels()
                }
            }
        }
    }
    
    dataTask.resume()
}
</code></pre>
<p>This is very similar, so I’ll only touch on the differences. The quoteURL calls a different function, &#8220;CURRENCY_EXCHANGE_RATE&#8221;. In addition to the Api key, there are parameters for &#8220;from_currency&#8221; and &#8220;to_currency&#8221;. I’m only interested in converting to US dollars, so I hardcoded “USD” as the &#8220;to_currency&#8221;. The &#8220;from_currency&#8221; is pulled from our text field. Once we get our data, we unpackage the JSON and update our labels. We don’t need to use all of the data this time, mostly since we’re using a fixed &#8220;to_currency&#8221;.</p>
<h2>Finishing up the Code</h2>
<p>The query code was the heavy lifting. We just need to make everything works gracefully now. It’s time to implement the resetLabels function that we called in case of an error. Here it is for <em>FirstViewController</em>.</p>
<pre><code>func resetLabels() {
    stockSymbolLabel.text = "";
    stockOpenLabel.text = "";
    stockHighLabel.text = "";
    stockLowLabel.text = "";
    stockPriceLabel.text = "";
    stockVolumeLabel.text = "";
    stockLastTradingDayLabel.text = "";
    stockPreviousCloseLabel.text = "";
    stockChangeLabel.text = "";
    stockChangePercentLabel.text = "";
}
</code></pre>
<p>Now go to <em>SecondViewController</em> and add the equivalent code.</p>
<pre><code>func resetLabels() {
    currencyCodeLabel.text = "";
    currencyNameLabel.text = "";
    currencyExchangeRateLabel.text = "";
    currencyLastRefreshedLabel.text = "";
    currencyBidValue.text = "";
    currencyAskValue.text = "";
}
</code></pre>
<p>We also need some code to make sure the keyboard dismisses appropriately and shifts the screen up so we can view what we’re typing. Make sure both of our view controllers are UITextFieldDelegates by adding UITextFieldDelegate at the top of the class.</p>
<pre><code>class FirstViewController: UIViewController, UITextFieldDelegate {
</code></pre>
<p>Here’s the other one.</p>
<pre><code>class SecondViewController: UIViewController, UITextFieldDelegate {
</code></pre>
<p>Put these keyboard functions in both view controllers.</p>
<pre><code>@objc func dismissKeyboard() {
    //Causes the view (or one of its embedded text fields) to resign the first responder status.
    view.endEditing(true)
}

@objc func keyboardWillShow(notification: NSNotification) {
    if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
        if self.view.frame.origin.y == 0 {
            self.view.frame.origin.y -= keyboardSize.height
        }
    }
}

@objc func keyboardWillHide(notification: NSNotification) {
    if self.view.frame.origin.y != 0 {
        self.view.frame.origin.y = 0
    }
}
</code></pre>
<p>We need to set up notifications for these functions in viewDidLoad and establish each class as its text field’s delegate. This is also a good chance to clear out the placeholder text in our value labels. Here’s the entirety of viewDidLoad for the stock view controller.</p>
<pre><code>override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    resetLabels()
    
    self.stockTextField.delegate = self
    
    let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIInputViewController.dismissKeyboard))
    view.addGestureRecognizer(tap)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
</code></pre>
<p>Click on over to the currency view controller to do something similar.</p>
<pre><code>override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    resetLabels()
        
    self.currencyTextField.delegate = self
        
    let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIInputViewController.dismissKeyboard))
    view.addGestureRecognizer(tap)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
</code></pre>
<p>We’d like to fire off queries from either tapping the search button or hitting return on the keyboard. We’ll handle the former by filling out our IBAction functions and the latter is a delegate method for the keyboard. Here’s the code for the stock view controller.</p>
<pre><code>@IBAction func stockSearchTapped(_ sender: Any) {
    getStockQuote()
    dismissKeyboard()
}

func textFieldShouldReturn(_ textField: UITextField) -&gt; Bool {
    getStockQuote()
    self.view.endEditing(true)
    return false
}
</code></pre>
<p>Here’s the equivalent code for the currency.</p>
<pre><code>
@IBAction func currencySearchButtonTapped(_ sender: Any) {
    getCurrencyQuote()
    dismissKeyboard()
}

func textFieldShouldReturn(_ textField: UITextField) -&gt; Bool {
    getCurrencyQuote()
    self.view.endEditing(true)
    return false
}
</code></pre>
<p>That should cover everything! Run the app and check your favorite stock. Check your least favorite stock. How about a currency? Remember to use codes for the currency, like EUR for euro or JPY for yen. You can even do digital currencies likes BTC for bitcoin.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2019/08/Quotes-AAPL.png"><img decoding="async" class="alignnone size-medium wp-image-2034" src="https://hangzone.com/wp-content/uploads/2019/08/Quotes-AAPL-139x300.png" alt="Quotes AAPL" width="139" height="300" srcset="https://hangzone.com/wp-content/uploads/2019/08/Quotes-AAPL-139x300.png 139w, https://hangzone.com/wp-content/uploads/2019/08/Quotes-AAPL-473x1024.png 473w, https://hangzone.com/wp-content/uploads/2019/08/Quotes-AAPL.png 1125w" sizes="(max-width: 139px) 100vw, 139px" /></a></p>
<h2>Conclusion</h2>
<p>We’re just scratching the surface of investment app possibilities. You can fetch all sorts of historical data and technical indicators. Take a look around the documentation at Alpha Vantage and see if anything catches your eye. If you see something you like, just amend the quoteURL in your code and do a test run to see what sort of data you get back. Once you’ve had a look at the structure of the data, you can figure out what keys to use to unpack the dictionaries. Good luck and thanks for reading the HangZone blog!</p>
<p>The post <a href="https://hangzone.com/how-to-fetch-stock-and-currency-quotes-for-iphone/">How to Fetch Stock and Currency Quotes for iPhone</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>A Breakdown of Dr. Mario World&#8217;s Financial Performance</title>
		<link>https://hangzone.com/a-breakdown-of-dr-mario-worlds-financial-performance/</link>
		
		<dc:creator><![CDATA[Tyler Bandy]]></dc:creator>
		<pubDate>Wed, 31 Jul 2019 15:23:59 +0000</pubDate>
				<category><![CDATA[Game Industry]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=2015</guid>

					<description><![CDATA[<p>I’m a huge fan of the original Dr. Mario on NES. I suspect it’s the best classic puzzle game of all-time. I’ve beaten level 20 on speed high hundreds of ...</p>
<p>The post <a href="https://hangzone.com/a-breakdown-of-dr-mario-worlds-financial-performance/">A Breakdown of Dr. Mario World&#8217;s Financial Performance</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>I’m a huge fan of the original Dr. Mario on NES. I suspect it’s the best classic puzzle game of all-time. I’ve beaten level 20 on speed high hundreds of times, usually with the Fever music. Sometimes I’ve even cleared it with the Chill music! Spoiler—the ending cutscene on high difficulty reveals that the viruses are aliens! I don’t recall a twist like that in Tetris!</p>
<p>Here we are roughly 30 years later, and Nintendo has just released Dr. Mario World in the App Store. It’s still a puzzle game with the goal of annihilating the viruses, but now you only have to match three of a color instead of four to erase it. How quaint. Now I’m the one who feels like an alien in this modern retelling of Dr. Mario’s pharmaceutical adventures.</p>
<p>The game’s fun. Everyone in the HangZone office is in the top tier on versus mode, and I was the 198th person to clear the special stage in World 2.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2019/07/Dr-Mario-World-Medal.png"><img decoding="async" class="alignnone wp-image-2016 size-medium" src="https://hangzone.com/wp-content/uploads/2019/07/Dr-Mario-World-Medal-139x300.png" alt="Dr Mario World Medal" width="139" height="300" srcset="https://hangzone.com/wp-content/uploads/2019/07/Dr-Mario-World-Medal-139x300.png 139w, https://hangzone.com/wp-content/uploads/2019/07/Dr-Mario-World-Medal-473x1024.png 473w, https://hangzone.com/wp-content/uploads/2019/07/Dr-Mario-World-Medal.png 1125w" sizes="(max-width: 139px) 100vw, 139px" /></a></p>
<h2>Financial Performance</h2>
<p>The fun factor is all good and well, but we’re here to talk about revenue. Specifically, let’s consider this chart that Sensor Tower compiled earlier this month. You can see the opening three days of revenue and installs for each of Nintendo’s App Store titles.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2019/07/Nintendo-72-Hours-Revenue.png"><img decoding="async" class="alignnone wp-image-2017 size-large" src="https://hangzone.com/wp-content/uploads/2019/07/Nintendo-72-Hours-Revenue-1024x760.png" alt="Nintendo 72 Hours Revenue" width="800" height="594" srcset="https://hangzone.com/wp-content/uploads/2019/07/Nintendo-72-Hours-Revenue-1024x760.png 1024w, https://hangzone.com/wp-content/uploads/2019/07/Nintendo-72-Hours-Revenue-300x223.png 300w, https://hangzone.com/wp-content/uploads/2019/07/Nintendo-72-Hours-Revenue.png 1448w" sizes="(max-width: 800px) 100vw, 800px" /></a></p>
<p>On the surface, Dr. Mario World looks like a financial flop, and many news outlets are saying as much based on Sensor Tower’s numbers. I’d like to break things down a little more carefully, and see if we can figure out exactly what the problem is. Let’s consider the different franchises, the varying game styles, and the corresponding financial results.</p>
<h2>Super Mario Run</h2>
<p>For starters, let’s throw out Super Mario Run from the discussion. It’s effectively a paid game. More specifically, it’s a free game with a one-time paywall after a couple of demo levels. <a href="https://hangzone.com/a-lesson-in-paywalls-from-super-mario-run/">We discussed this monetization model in more detail when the game was released.</a> We know that paid games tend to have a lower upper bound for revenue potential than free games, but the revenue is obviously front-loaded due to the upfront price tag. Consequently, it’s not fair to compare Super Mario Run’s opening three days to Nintendo’s other App Store games which stand to make more money off of users after the initial three days.</p>
<h2>Fire Emblem: Heroes</h2>
<p>Fire Emblem is a hugely popular franchise in Japan. Fire Emblem: Heroes uses the gacha model that’s found in Puzzle and Dragons and many other top grossing Japanese titles. <a href="https://hangzone.com/gacha-mobile-app-monetization/">We’ve discussed the gacha style in the past.</a> Typically, you spend in-game currency to collect random characters for your team. The most desirable characters usually have a 2% chance or less of appearing. You can convert real money to in-game currency to pull more characters. Over time, gacha games will introduce harder content and better characters to keep its players chasing the latest and greatest characters.</p>
<p>Gacha remains arguably the most effective revenue model in gaming. It’s not without its critics, however. Gacha mechanics are heavily regulated in Japan. Europe is actively debating the legality of loot boxes, which are effectively the same mechanic. Many prominent gacha games have shutdown their European servers as a result.</p>
<p>Having said all of this, it’s no surprise that Fire Emblem: Heroes is leading the pack for Nintendo in revenue by a huge margin The combination of a strong franchise and proven revenue model is a recipe for success.</p>
<h2>Animal Crossing: Pocket Camp</h2>
<p>Animal Crossing already had a history on Nintendo’s own handheld devices. Furthermore, the games already featured doing things with timers, such as growing crops. Sounds like an easy translation to a freemium mobile game, right?</p>
<p>Animal Crossing lets players speed up the timers with in-game currency, similar to Clash of Clans. It’s a proven freemium model. It’s not as effective as top gacha models these days, but it works. Animal Crossing’s revenue has been solid, but not as good as Fire Emblem&#8217;s gaudy results. Despite the recognizable brand name in the US, the game still makes most of its revenue in Japan. This is of course true of Fire Emblem and Dragalia Lost as well. Japanese players are now spending up to three times per user compared to the US in many top games, so a recognizable brand in the west may not be as important as you think.</p>
<h2>Dragalia Lost</h2>
<p>Dragalia Lost is a Japanese style gacha title, but without the name brand of Fire Emblem. It also didn’t launch in as many markets. This makes the lighter revenue and installs understandable. Before you write-off Dragalia Lost as a small-time effort, however, let’s extend our horizon out to one month.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2019/07/Nintendo-One-Month-Revenue.png"><img decoding="async" class="alignnone wp-image-2018 size-large" src="https://hangzone.com/wp-content/uploads/2019/07/Nintendo-One-Month-Revenue-1024x685.png" alt="Nintendo One Month Revenue" width="800" height="535" srcset="https://hangzone.com/wp-content/uploads/2019/07/Nintendo-One-Month-Revenue-1024x685.png 1024w, https://hangzone.com/wp-content/uploads/2019/07/Nintendo-One-Month-Revenue-300x201.png 300w, https://hangzone.com/wp-content/uploads/2019/07/Nintendo-One-Month-Revenue.png 1446w" sizes="(max-width: 800px) 100vw, 800px" /></a></p>
<p>Dragalia Lost finished second only to Fire Emblem in first month revenue among Nintendo’s App Store games. Moreover, it accomplished this feat by making $25 per install, over 4x what Fire Emblem earned during its first month. Fire Emblem has since surpassed Dragalia Lost on revenue per user. Fire Emblem now stands at $38 per user, Dragalia Lost at $33, and Animal Crossing at $3.30 (<a href="https://sensortower.com/blog/dragalia-lost-revenue-100-million">Sensor Tower</a>). Nevertheless, they’ve performed exceedingly well. Another point for gacha.</p>
<h2>Nintendo’s Predicament</h2>
<p>After effectively cracking the code to making money in the App Store, Nintendo seemed to be feeling a little sheepish about making so much money. I’ve been a fan of Nintendo since the NES in the 1980s. They’ve consistently put out high quality content. They’ve created several great franchises. Nintendo has more goodwill than any game developer. Freemium titles often turn off traditional gamers, so the company has reason to tread carefully and not agitate its loyal console customer base. They’ve gone so far as to suggest in their earnings call and other interviews that they don’t want gamers to spend too much money on in-app purchases in their mobile games (<a href="https://www.wsj.com/articles/nintendo-to-smartphone-gamers-dont-spend-too-much-on-us-11551864160?">Wall Street Journal</a>).</p>
<p>This puts Nintendo in an odd sort of connundrum. They know how to print money on mobile, but they don’t want to do it at the expense of long-term profits and goodwill. So what do you do?</p>
<h2>Dr. Mario World Revenue Model</h2>
<p>How about a different flavor of freemium! Dr. Mario World’s revenue model looks like Candy Crush at first glance. You have five hearts. You use one to play each level, and get one back on your first clear. This allows you to keep going as long as you’re winning. If you run out of hearts, you can buy more. You can also use items in levels, which cost in-game money. This is the typical revenue model for a casual match 3 game.</p>
<p>In an interesting twist, they added a gacha element. You can get different doctors and assistants that have different abilities. Some are more useful for single player, while others are good for versus. Drawing multiples levels them up. Typical gacha.</p>
<h2>Casual Gacha</h2>
<p>Well, it’s typical gacha as far as rates go, but the problem is that there isn’t as much depth as other gacha titles. There are only 10 doctors and 32 assistants. Many of the assistants’ skills are the same. For the sake of comparison, my favorite gacha game, Puzzle and Dragons, has thousands of characters. Puzzle and Dragons teams have to be carefully assembled to handle all of the dungeon mechanics. In Dr. Mario World, the characters’ abilities aren’t as important for success. My characters’ skills typically don’t have a dramatic impact on whether I complete a level or not. The choice of doctor is important for versus mode, due to substantial match-up advantages (i.e. Toadette crushes Bowser, but struggles against Peach due to her block stat against the #3 attack). However, there’s no way to know which character you’ll play against. Even if I leveled up my characters more, those match-up advantages are so exaggerated that it probably won’t help me much, other than ditto matches without the match-up advantages. This all makes the gacha element less exciting to roll than in other games.</p>
<p>Furthermore, I think the Candy Crush style monetization elements turn off more serious players. The target audience for American casual match 3 games is women over 40. The target audience for Japanese gacha games skews toward the more traditional core gamer. This audience is used to games that don’t have monetization efforts outside of the gacha and stamina refresh. I think the items in Dr. Mario World may serve to cheapen the experience in some players’ minds.</p>
<p>Finally, Dr. Mario isn’t the same caliber of franchise as Fire Emblem. It may be more well known in the US, but when the big revenue is coming out of Japan on mobile, that’s of little consequence.<span class="Apple-converted-space"> </span></p>
<h2>Final Thoughts</h2>
<p>While these download and revenue figures don’t look very compelling for Dr Mario World, Sensor Tower notes that it’s actually roughly in-line with King.com&#8217;s recent match 3 titles. On the one hand, you can consider that solid news. Nintendo delivered a match 3 variant that’s doing as well as the traditional western leader in match 3 games. On the other hand, I’m more inclined to wonder if we’re in the latter days of the casual match 3 era. When Activision bought King.com, they paid a much lower P/E ratio than the average multiple for the S&amp;P 500. The takeout price didn’t suggest the rosiest outlook for growth prospects. It seems that the mobile gaming world has indeed moved on to new things.</p>
<p>For what it’s worth, I think Dr. Mario World is a really unique and fun twist on match 3. It’s not the single move gameplay people think of with Candy Crush. We know complex match 3 with gacha works—prime example being Puzzle and Dragons. If Nintendo really wanted to maximize revenue, they could have focused more on the gacha, possibly scratched the items, and deepened the character complexity. Of course, we also know Nintendo’s primary goal is not to maximize revenue in the App Store. One of Activision’s goals with the King.com acquisition was to convert casual match 3 gamers over to its console titles. Maybe Nintendo also wanted to reach out to the casual crowd with Dr. Mario World and intentionally refrained from using lucrative complexity that might thwart this effort. It’s too early to say for sure, or judge it as a success or failure. We’ll just have to wait and see. Until then, thanks for reading the HangZone blog.</p>
<p>The post <a href="https://hangzone.com/a-breakdown-of-dr-mario-worlds-financial-performance/">A Breakdown of Dr. Mario World&#8217;s Financial Performance</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>iPadOS and More from WWDC 2019</title>
		<link>https://hangzone.com/ipados-more-wwdc-2019/</link>
		
		<dc:creator><![CDATA[Judson Bandy]]></dc:creator>
		<pubDate>Fri, 28 Jun 2019 21:49:40 +0000</pubDate>
				<category><![CDATA[Developer Insights]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=2007</guid>

					<description><![CDATA[<p>Earlier this month, Apple held its annual Worldwide Developers Conference, known as WWDC. The event is an annual tradition for Apple with the inaugural WWDC held in 1987. You may ...</p>
<p>The post <a href="https://hangzone.com/ipados-more-wwdc-2019/">iPadOS and More from WWDC 2019</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Earlier this month, Apple held its annual Worldwide Developers Conference, known as WWDC. The event is an annual tradition for Apple with the inaugural WWDC held in 1987. You may be familiar with Apple’s spectacular press conferences where they announce new iPhones to get everyone excited about their latest products. This is a similar event, but at WWDC, the target audience is developers. Upcoming software, platforms, and tools are the shiny new objects put on display. WWDC always leaves developers with a lot to be excited about, and the 2019 version was no different. Let’s take a quick look around some of the highlights.</p>
<h2>General Mac Highlights</h2>
<p>A new operating system was introduced called MacOS Catalina. This continues the California theme that has been used to name the recent versions of MacOS. After a long run, the iTunes app is being replaced with separate Music, Podcast, and Apple TV apps. This isn’t especially meaningful for developers, but it’s alway interesting to take note of changes that Apple makes to its first party apps. The Mac and iPad will also be able to work better together than ever before. Through a new app called Sidecar, the iPad will be able to be used as a second display for your Mac either wirelessly or with a wired connection. This has been possible through third-party apps before, but it’s exciting to have Apple support and extend the functionality. Besides traditional second screen use, this has some cool possibilities for using the iPad as a drawing tool with the Apple Pencil where the devices are both working in the same app.<span class="Apple-converted-space"> </span></p>
<p>Although this isn’t typically a hardware conference, a new Mac Pro computer was also introduced. The computer is available with up to a 28-core processor and is an impressive high-end machine. To go along with it, a new 32 inch 6K monitor was announced. My favorite tidbit about the monitor is the stand that Apple made for it. The stand can be purchased for an additional $999. At that price, I can only imagine how majestic it sits on a desk and caresses your monitor (but it seems like it should probably make your coffee too).</p>
<h2>General iOS Highlights</h2>
<p>The new operating system, iOS 13, comes with a number of enhancements as well. A new dark mode has been one of the most talked about features. A dark mode has already been available for Mac, but this is its first appearance on mobile. The feature will darken your background and change to a lighter text color so that it is easier on your eyes at night. This is absolutely relevant to developers, as it offers a new appearance for users to view your app. Typically if users are requesting a dark mode, you would like to have your app enabled to show a mode consistent with that display.<span class="Apple-converted-space"> </span></p>
<p>Apple has also introduced a new “swipe” keyboard named QuickPath. This has been in Android for some time and can be handy for one handed typing. The issue has always been how well it can guess what you are trying to type. We’ll gauge its quality with time. Apple also announced a new “Sign in With Apple” feature. This is to compete with Facebook, Google, and other third party log-ins that are used in apps, and it will be based on users existing Apple IDs. This is of particular note because it will be required as an option for log-in in apps where third party log-in options are offered.</p>
<h2>iPadOS</h2>
<p>All of these are nice, but what I really want to highlight is iPadOS and then quickly mention Project Catalyst. First, we will start with iPadOS. For the last several years, Apple has been slowly positioning the iPad as something closer to a full blown computer. This involved giving it a “Files” folder, attaching a keyboard, several enhancements to multitasking, and drag and drop features while using multiple apps to name a few things. Well, Apple is taking a step further in differentiating the iPad from the iPhone by giving it a dedicated operating system called iPadOS. This immediately brings a lot of questions to mind, so let me clear a few things up before going any further.</p>
<p>First, your current iPad apps will continue to work on iPadOS. This operating system is based on iOS, and is still in parallel with iOS. It is called iPadOS 13 to mirror iOS 13. The primary reason for the renaming is to better differentiate that the iPad and iPhone have different sets of features. As a basic example, Apple pencil support is only available on the iPad, so it’s easier to understand that it’s an iPadOS feature. As more and more features are only available for the iPad, and specifically new iPads, compatibility is easier to understand through the operating system.</p>
<p>Speaking of compatibility, what devices are eligible to upgrade to iPadOS 13? It requires devices that use an Apple A8/A8X chip or later and have over 1 GB of RAM. This includes all iPad Pro models, iPad Air 2nd generation and newer, iPad mini 4th generation and newer, and iPad 5th generation and newer.</p>
<h2>What’s new with iPadOS</h2>
<p>Now that we’ve covered compatibility and reasoning for the operating system name, what exactly does is it do? Well, the first thing that you will notice is a new home screen. You will still see a grid of app icons like before, but they have been moved to the right. This frees up the left side for the time, date, and Apple’s widgets. The dock of app’s is still at the bottom. None of the elements are exactly new, but it seems to be a nice design and a further departure from the traditional iOS look to accompany the new name.</p>
<p>Safari has also gotten a substantial upgrade. It retrieves full versions of websites, same as you would expect on a Mac. You can still easily request the mobile version from a toolbar button if that’s what you’re after. We also now have the ability to easily manage and see downloads, just like on the Mac version of Safari. Having greater control and ability to manage files is a crucial piece to being able to use the iPad more like a full computer.</p>
<p>This bring us nicely into talking about the Files app. You can now connect a USB drive to your Mac, and it shows up nicely in your Files app. Along with integration with Google Drive and Dropbox, the Files app finally allows you to manage apps like you would expect. It may not seem like a lot, but again, this is key to using an iPad for greater productivity. The Files app also lets you drill down by columns, like in Finder on Mac, so you can navigate with ease.</p>
<p>Finally, mouse support has been added. It doesn’t appear to be a core feature by any means, but the support exists. It’s an AssistiveTouch feature under the Accessibility section of Settings.<span class="Apple-converted-space">  </span>Given where and how it is included, mouse support inside your app is not necessary as the system still works off of touches and gestures. It is certainly something to note as it is likely a sign of something to come in the future though.</p>
<p>There are a number of other multitasking and performance improvements that go along to round out the changes for iPadOS.</p>
<h2>Project Catalyst</h2>
<p>Finally, let’s talk about Project Catalyst. This is new, but it was mentioned at WWDC 2018 under the name of Project Marzipan. Apple was using the project internally to build a single app that would work for iOS and macOS. That’s a big deal! Many of Apple’s apps, such as News, Home, and Stocks are apparently built with the the technology. Cross platform building between the Mac and iOS isn’t fully there yet, but this certainly is an encouraging step in the right direction.</p>
<p>Project Catalyst is an SDK for Xcode that will allow developers to convert iPad apps to Mac apps. The app must have an iPad version (it doesn’t work for iPhone only apps). The developer would then be able to submit versions to the iOS and Mac app stores separately. A universal app between the platforms is not available at this time. I have yet to use it, so I can’t speak to the ability to tweak changes to make your app more Mac friendly and whatnot, but it is certainly exciting and could make developing cross platform software for the two systems substantially faster.</p>
<h2>Conclusion</h2>
<p>WWDC 2019 was packed with many exciting new features. For app developers, any time we hear about completely new operating systems, like iPadOS, there is an initial fear of backwards compatibility for our current apps and excitement for what’s new. Luckily, iPadOS isn’t too much of a deviation from the previous version and is more about an exciting future. If you are interested in learning more about any of these features or other happenings from WWDC, I strongly recommend checking out Apple’s site. We didn’t come close to covering everything. As always, thanks for reading the HangZone blog!</p>
<p>The post <a href="https://hangzone.com/ipados-more-wwdc-2019/">iPadOS and More from WWDC 2019</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
