<?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>Tyler Bandy, Author at HangZone</title>
	<atom:link href="https://hangzone.com/author/tyler/feed/" rel="self" type="application/rss+xml" />
	<link>https://hangzone.com/author/tyler/</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>Tyler Bandy, Author at HangZone</title>
	<link>https://hangzone.com/author/tyler/</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>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>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>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>How to Add Core Location to our Weather App</title>
		<link>https://hangzone.com/add-core-location-weather-app/</link>
		
		<dc:creator><![CDATA[Tyler Bandy]]></dc:creator>
		<pubDate>Tue, 28 May 2019 14:35:27 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=1994</guid>

					<description><![CDATA[<p>It’s been over a year since we tackled weather on the HangZone blog. Longtime readers may recall our original weather app tutorial, followed by our five day forecast tutorial. In ...</p>
<p>The post <a href="https://hangzone.com/add-core-location-weather-app/">How to Add Core Location to our Weather App</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>It’s been over a year since we tackled weather on the HangZone blog. Longtime readers may recall our <a href="https://hangzone.com/build-ios-weather-app-external-api/">original weather app tutorial</a>, followed by our <a href="https://hangzone.com/display-five-day-forecasts-weather-app/">five day forecast tutorial</a>. In those apps, we called the OpenWeatherMap API to get the weather information for our city. Next, we unpacked the JSON results to display the appropriate information on the screen. The end result works…but there’s a catch.</p>
<p>Since we hard-coded a specific city into our API call, the data is only useful if we’re in the right city. If you tend to stay in the same place, no problem! You don’t need to worry about this. Perhaps you find yourself traveling around a bit though. Maybe you’ve even moved to a new city since you originally built your own weather app. Wouldn’t it be nice if your app just automatically detected your location?</p>
<p>Today we’ll use Apple’s Core Location framework to upgrade our 5 day forecast app to automatically query results for the user’s location. Go make a copy of your old weather project, and fire up Xcode.</p>
<h2>Choose the Right Level of Privacy</h2>
<p>In order to track a user’s location automatically, we first have to convince the user to grant our app permission to do so. These days, users are more concerned about privacy than ever, so they don’t want to give apps more information than absolutely necessary. Furthermore, location tracking can drain your iPhone’s battery relatively quickly if you use it constantly. Consequently, users are all the more reluctant to enable location tracking.</p>
<p>Fortunately, Apple has multiple degrees of location tracking that apps can use. Although this can make things more confusing for the developer, the upside is that users shouldn’t have to grant excessive tracking abilities to apps that don’t need them.</p>
<p>For our purposes, <i>When-in-use authorization</i> will suit our needs perfectly, plus it’s more efficient than <i>Always authorization</i>. The names are fairly self-explanatory. The primary difference is that <i>Always authorization</i> can continue to track location data while the app is in the background. Each authorization level supports various Core Location services. <i>Always authorization</i> does support more services, but <i>When-in-use authorization</i> covers the <i>Standard Location Service</i> that we’ll need for this app.</p>
<h2>Set-up info.plist for Core Location</h2>
<p>Now that we’ve decided what sort of tracking we want to do, it’s time to set-up our Core Location code. We’ll start with the <em>info.plist</em> file. In an effort to better educate users on why apps need to infringe on users’ privacy, Apple makes developers offer an explanation for the use of various services, including Core Location.</p>
<p>The key we’re looking for is <i>NSLocationWhenInUseUsageDescription</i>. The more human-readable version of the key displayed in Xcode is <i>Privacy &#8211; Location When In Use Usage Description</i>. You can write whatever you want for the description. I used the following.</p>
<pre><code>We need to check your location in order to let you know the weather forecast.
</code></pre>
<p>Seems like a reasonable request, right? With that out of the way, let’s write some code!</p>
<h2>Request the User’s Location</h2>
<p>The first thing we need to do is get the user’s approval to use their location. Normally, it’s best to wait until you absolutely need whatever data you’re requesting before asking the user for it. Again, users are reluctant to allow anything that seems like overreaching, so it’s nice to onboard them in the app a little bit before asking about a tracking service.</p>
<p>Considering this a weather app and tracking is obviously necessary, we’re going to jump the gun and ask for tracking permission immediately. Open <i>ViewController.swift </i>and make our view controller a Core Location Manager Delegate by adjusting the class line at the top.</p>
<pre><code>class ViewController: UIViewController, CLLocationManagerDelegate {
</code></pre>
<p>Now we can receive callbacks on Core Location events. Let’s go ahead and define the Location Manager, so we can get some events. Put the following constant just below our IBOutlets.</p>
<pre><code>let locationManager = CLLocationManager()
</code></pre>
<p>Now we’ll add some functions to go with it. You can put them anywhere inside the class braces.</p>
<pre><code>func enableBasicLocationServices() {
    locationManager.delegate = self

    switch CLLocationManager.authorizationStatus() {
    case .notDetermined:
        // Request when-in-use authorization initially
        locationManager.requestWhenInUseAuthorization()
        break

    case .restricted, .denied:
        // Disable location features
        clearWeather()
        break

    case .authorizedWhenInUse, .authorizedAlways:
        // Enable location features
        getWeather()
        break
    }
}

func locationManager(_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus) {
    switch status {
    case .notDetermined, .authorizedAlways:
        locationManager.requestWhenInUseAuthorization()
        break

    case .restricted, .denied:
        clearWeather()
        break

    case .authorizedWhenInUse:
        getWeather()
        break
    }
}
</code></pre>
<p>We’ll call our <i>enableBasicLocationServices </i>function manually to get the user’s tracking permission. If we haven’t asked the user before, they’ll get a prompt with our request. Otherwise, we’ll use their previous answer. If they allow us to get their location, we’ll get the weather. Otherwise, we’ll call a new function—<i>clearWeather</i>. We also have a similar switch statement in our <i>locationManagerDidChangeAuthorization</i> function. Users are capable of changing authorization whenever they want through settings, so we have to adjust accordingly.</p>
<p>Go ahead and define clearWeather.</p>
<pre><code>func clearWeather() {
    self.weatherLabel1.text = "Day 1: ?"
    self.weatherLabel2.text = "Day 2: ?"
    self.weatherLabel3.text = "Day 3: ?"
    self.weatherLabel4.text = "Day 4: ?"
    self.weatherLabel5.text = "Day 5: ?"
}
</code></pre>
<p>Five question marks is about the best we can do for a forecast if they won’t give us a location! Now replace the call to <i>getWeather</i> in <i>viewDidLoad</i> with a call to <i>enableBasicLocationServices</i>.</p>
<pre><code>override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view, typically from a nib.
    enableBasicLocationServices()
}
</code></pre>
<p>That will set cogs in motion!</p>
<h2>Interpret the Location</h2>
<p>Assuming the user approves location tracking, we still need to get their location and pass it into the OpeanWeatherMap API. Our previous API call used a city name, but we can also use latitude and longitude coordinates. That’s more convenient and precise since we have them readily available with Core Location. Our <i>getWeather </i>function will look the same, other than we now get our <i>weatherURL</i> in the following manner.</p>
<pre><code>var currentLocation: CLLocation!

if( CLLocationManager.authorizationStatus() == .authorizedWhenInUse || CLLocationManager.authorizationStatus() == .authorizedAlways){

    currentLocation = locationManager.location

} else {
    clearWeather()
    return
}

let latitude = currentLocation.coordinate.latitude
let longitude = currentLocation.coordinate.longitude

let session = URLSession.shared

let weatherURL = URL(string: “http://api.openweathermap.org/data/2.5/forecast?lat=\(latitude)&amp;lon=\(longitude)&amp;units=imperial&amp;APPID=XXX")!
</code></pre>
<p>The coordinate property on currentLocation is an optional, so I put in one more check to make sure the user has granted proper authorization. This also covers us if the user reloads the forecast with the onscreen button, which currently calls <i>getWeather</i> without checking on the Core Location authorization. Latitude and longitude are available off the coordinate property, and then all we do is plug them into our slightly modified API string. I replaced my API key with <i>XXX</i>. Use whatever your API key is, just as in the previous iterations of this app.</p>
<p>Run the app and see what happens!</p>
<p><a href="https://hangzone.com/wp-content/uploads/2019/05/Core-Location-Weather-Permission.png"><img decoding="async" class="alignnone size-medium wp-image-1995" src="https://hangzone.com/wp-content/uploads/2019/05/Core-Location-Weather-Permission-139x300.png" alt="Core Location Weather Permission" width="139" height="300" srcset="https://hangzone.com/wp-content/uploads/2019/05/Core-Location-Weather-Permission-139x300.png 139w, https://hangzone.com/wp-content/uploads/2019/05/Core-Location-Weather-Permission-473x1024.png 473w, https://hangzone.com/wp-content/uploads/2019/05/Core-Location-Weather-Permission.png 1125w" sizes="(max-width: 139px) 100vw, 139px" /></a><a href="https://hangzone.com/wp-content/uploads/2019/05/Core-Location-Weather-Forecast.png"><img decoding="async" class="alignnone size-medium wp-image-1996" src="https://hangzone.com/wp-content/uploads/2019/05/Core-Location-Weather-Forecast-139x300.png" alt="Core Location Weather Forecast" width="139" height="300" srcset="https://hangzone.com/wp-content/uploads/2019/05/Core-Location-Weather-Forecast-139x300.png 139w, https://hangzone.com/wp-content/uploads/2019/05/Core-Location-Weather-Forecast-473x1024.png 473w, https://hangzone.com/wp-content/uploads/2019/05/Core-Location-Weather-Forecast.png 1125w" sizes="(max-width: 139px) 100vw, 139px" /></a></p>
<p>Did your message from earlier convince you to grant yourself tracking privileges? I hope so! Try driving a few states over and reloading the data. Pretty nifty, right!</p>
<h2>Conclusion</h2>
<p>This is just a taste of what you can do with Core Location, but it’s a very practical taste and often all you need. Our weather app is moving on up in the world. Maybe we&#8217;ll add another feature to it next year! Until next time, have fun thinking of the crazy things you can do with Core Location’s <i>Always authorization</i>!</p>
<p>The post <a href="https://hangzone.com/add-core-location-weather-app/">How to Add Core Location to our Weather App</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Transfer Data More Efficiently with DTOs</title>
		<link>https://hangzone.com/transfer-data-efficiently-dtos/</link>
		
		<dc:creator><![CDATA[Tyler Bandy]]></dc:creator>
		<pubDate>Mon, 29 Apr 2019 13:53:29 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Developer Insights]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=1986</guid>

					<description><![CDATA[<p>Let’s say you’re building an application with a SQL database stored in the cloud. You’ve written a web API which you can call from you app to interact with the ...</p>
<p>The post <a href="https://hangzone.com/transfer-data-efficiently-dtos/">How to Transfer Data More Efficiently with DTOs</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Let’s say you’re building an application with a SQL database stored in the cloud. You’ve written a web API which you can call from you app to interact with the database. As you start to populate your database with test data, you realize your queries aren’t processing as fast as you like. You go to check out your database metrics and you realize the amount of data transferred out of the database is much higher than you expected. Not only is this slow—it’s going to get expensive! This can be a scary experience, but fear not! There may be multiple problems in play, but we can (hopefully) remedy most of them with data transfer objects (DTOs).</p>
<h2>What’s Causing the Problem</h2>
<p>If your outgoing data is is dramatically more than you expected, you should stop and consider what exactly your queries are returning. Imagine a database with two tables: Customers and Orders. The corresponding classes look like this (we’ll use C# today):</p>
<pre><code>public class Customer
{
    public Customer() { }

    public int ID { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string Address { get; set; }

    public string City { get; set; }

    public string State { get; set; }

    public string ZipCode { get; set; }

    public string Phone { get; set; }

    public string Email { get; set; }
}

public class Order
{
    public Order() { }

    public int ID { get; set; }

    public int CustomerID { get; set; }

    public DateTime Date { get; set; }

    public decimal Cost { get; set; }
}
</code></pre>
<p>The Customer class holds several pieces of information about each customer. Whenever a customer places an order, we get a new Order object that has some information about the order. Among the information stored in each Order is a CustomerID foreign key, which we can use to get the related customer information for that order.</p>
<p>After storing some data, we might like to query all of the Orders. Let’s say we want to display them in a table that includes a Customer Name column. Unfortunately, we don’t have the customer’s name stored directly in the Order model. In addition to querying the Orders, we’ll also have to query the Customer table for the CustomerIDs found on our Orders.</p>
<p>If you’re using an Object Relational Mapper (ORM) like Entity Framework to execute your database actions, you’ll probably use a navigation property to get the extra Customer information. You just add a new Customer property on the Order model with the following format.</p>
<pre><code>public class Order
{
    public Order() { }

    public int ID { get; set; }

    public int CustomerID { get; set; }

    <b>public Customer Customer { get; set; }</b>

    public DateTime Date { get; set; }

    public decimal Cost { get; set; }
}
</code></pre>
<p>The Customer property isn’t stored in the database as a field on Order. It’s merely added to the Order object if you choose to include it as part of your query. If you’re using Entity Framework, the code in your web API might look like this.</p>
<pre><code>return await _db.Orders
    .Include(Order =&gt; Order.Customer)
    .AsNoTracking()
    .ToListAsync();
</code></pre>
<p>Now if we want to get the customer’s name off of the order, we can easily get it as follows.</p>
<pre><code>string customerName = order.Customer.FirstName + " " + order.Customer.LastName;
</code></pre>
<p>That’s all good and well. It works. It’s just really inefficient. We’re returning all sorts of address and contract information for the customer on each Order object that we don’t need. If a customer has multiple orders, we’re returning all of that excess information multiple times!</p>
<p>This example only has one include, but things can get much worse if you layer more includes on top of includes. It’s time to rethink this approach with DTOs.</p>
<h2>What’s a DTO?</h2>
<p>Data Transfer Objects—DTOs for short—are objects that are optimized for sending information efficiently over the wire. Essentially, they cut out extra information. Your web API grabs the regular objects that you know and love out of the database. Before it sends them to your local application, the web api converts the objects to DTOs. Once your local application receives the DTOs, it converts the objects into a suitable format for display within the project&#8217;s UI.</p>
<p>This final UI form of the object may resemble the original model you got out of the database before the DTO conversion. It might also have some extra business logic on it. There are some people with very strong opinions about the stylebook surrounding DTO conversions, but there isn’t a definitive answer here. As long as the DTO carries an efficient amount of information and doesn’t have business logic, we’re doing just fine.</p>
<h2>Implementing a DTO solution</h2>
<p>Let’s return to our Customer and Order models. In order to avoid returning an entire Customer object along with each Order object, let’s write an OrderDTO model.</p>
<pre><code>public class OrderDTO
{
    public OrderDTO() { }

    public int ID { get; set; }

    public int CustomerID { get; set; }

    public string CustomerName { get; set; }

    public DateTime Date { get; set; }

    public decimal Cost { get; set; }

    OrderDTO(Order model)
    {
        ID = model.ID,
        CustomerID = model.CustomerID,
        Date = model.Date,
        Cost = model.Cost,
        CustomerName = model.Customer.FirstName + model.Customer.LastName;
    }
}
</code></pre>
<p>Notice that this model only has a string for CustomerName instead of an entire Customer property. We’ve also added a constructor to convert our Orders to OrderDTOs. Let’s see how that works with an Entity Framework call on the web API with orders of $100 or more.</p>
<pre><code>return await _db.Orders
    .Include(Order =&gt; Order.Customer)
    .AsNoTracking()
    .Where(Order =&gt; Order.Cost &gt;= 100)
    .Select(Order =&gt; new OrderDTO(Order))
    .ToListAsync();
</code></pre>
<p>We still do the same include as last time, so our initial Order object does have an entire Customer property on it. This version adds the conversion before we return the list, however, which flattens out our Orders to more efficient OrderDTOs. When we receive the OrderDTOs, we can go to town with them however we see fit. If we have a different Order model we want to use for the UI, we just write a constructor in our UI version of the model to convert the DTOs. We can even use the DTOs directly in the UI if we’re not worried about some stylebook vigilantes raising an eyebrow!</p>
<h2>Conclusion</h2>
<p>It’s easy to get carried away with queries that start compounding more queries and returning gigantic objects. These are the sort of situations where sending DTOs can be literally thousands of times more efficient from a data transfer standpoint than sending the original objects. I’ve primarily focused on flattening data with DTOs, but they’re also used for combining data into a single object that would otherwise require querying your API multiple times, effectively eliminating some roundtrips. Be creative. The fewer calls made and less data transferred, the better your app will perform!</p>
<p>The post <a href="https://hangzone.com/transfer-data-efficiently-dtos/">How to Transfer Data More Efficiently with DTOs</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to Make Your First Apple TV App</title>
		<link>https://hangzone.com/how-to-make-your-first-apple-tv-app/</link>
		
		<dc:creator><![CDATA[Tyler Bandy]]></dc:creator>
		<pubDate>Fri, 29 Mar 2019 13:20:09 +0000</pubDate>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Tutorials]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=1975</guid>

					<description><![CDATA[<p>We’ve written extensively on iPhone and iPad development in the past, but never covered the Apple TV. As you may know, you can mirror the screen of your iPhone or ...</p>
<p>The post <a href="https://hangzone.com/how-to-make-your-first-apple-tv-app/">How to Make Your First Apple TV App</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>We’ve written extensively on iPhone and iPad development in the past, but never covered the Apple TV. As you may know, you can mirror the screen of your iPhone or iPad onto your television using Apple TV. That’s a cool way to get your app on the big screen, but what if you want to build something native for Apple TV?<span class="Apple-converted-space">  </span>After all, the Apple TV 4K and Apple TV 4th generation have that fancy Siri Remote that would be fun to utilize. Today, we’ll start with the obligatory Hello World app, and highlight how to program for Apple TV’s unique control scheme.</p>
<h2>Initial Set-up</h2>
<p>I’m going to use an Apple TV 4K with Siri Remote for this tutorial. Fire up Xcode and select <em>tvOS</em> from the template categories. We’ll do a <em>Single View App</em> to keep things simple. I’m calling my project <em>HelloAppleTV</em>. Let’s use Swift for the language. Save the project wherever you like. You’ll see a familiar file structure on your left, similar to a typical iOS project. This is promising!</p>
<p>The next step is to hook up your Apple TV to your Mac so you can deploy apps to a real device. You’ll notice our only deployment options right now are simulators. This step is marginally trickier than hooking up your mobile devices since we can’t use a cable. First, turn on your Apple TV. Open the Settings app, choose <em>Remotes and Devices</em>, then drill down into <em>Remote App and Devices</em>. In Xcode, click the <em>Window</em> drop-down menu, and select <em>Devices and Simulators</em>. Assuming your devices are close enough, you’ll see a prompt on your Mac to pair your Apple TV. Click the pair button and enter the code displayed on your television. Your Mac now knows about your Apple TV!</p>
<p>Go ahead and close the <em>Devices and Simulators</em> window. In addition to the default simulators you can now select your actual Apple TV. Nice work! Let’s code something to celebrate.</p>
<h2>Hello World App</h2>
<p>Despite today’s contentious political climate, at least we can all agree that your first app on a new platform is supposed to display &#8220;Hello World&#8221;. Head over to <em>Main.Storyboard</em>, and you’ll find a blank view. This is basically the same thing you would expect in an iOS storyboard, only this view is shaped like a TV screen. Click the <em>Library</em> button to reveal the list of objects we can use. Drag a label out to the middle of the screen and write “Hello World”.</p>
<p>Sounds like a complete app to me. Go ahead and run the project. Xcode will give you some prompts about registering the Apple TV as a development device, assuming this is your first time using it. Fortunately, Xcode is good about handling these sort of things automatically now. Did you get the classic “Hello World” in black against a white background? Excellent. Time to try some more unique features of the platform.<span class="Apple-converted-space"> </span></p>
<h2>Add Some Buttons</h2>
<p>Normally, you physically tap buttons on an iPhone or iPad. That’s not an option on the TV, so let’s see for ourselves how this activity is handled by default. Add two buttons below your label. For this exercise, you can place them however you like. I placed them at the bottom of the screen, aligned horizontally with each other. I embedded them in a horizontal stack view, then used constraints to center the stack view horizontally and give it standard spacing relative to the bottom of the screen. I also used a constraint to make the buttons’ widths equal to each other. To round off the constraints, I went back and centered our &#8220;Hello World&#8221; label for good measure. In the left button, I set the text to Background. The right one says Text. My storyboard looks like this.</p>
<p><a href="https://hangzone.com/wp-content/uploads/2019/03/Apple-TV-Storyboard.png"><img decoding="async" class="alignnone wp-image-1979 size-large" src="https://hangzone.com/wp-content/uploads/2019/03/Apple-TV-Storyboard-1024x701.png" alt="Apple TV Storyboard" width="800" height="548" srcset="https://hangzone.com/wp-content/uploads/2019/03/Apple-TV-Storyboard-1024x701.png 1024w, https://hangzone.com/wp-content/uploads/2019/03/Apple-TV-Storyboard-300x205.png 300w, https://hangzone.com/wp-content/uploads/2019/03/Apple-TV-Storyboard.png 1496w" sizes="(max-width: 800px) 100vw, 800px" /></a></p>
<p>Now open up <em>Assistant Editor</em> mode, and link your label to <em>ViewController.swift</em> as <em>helloWorldLabel</em>. Link the buttons as actions—<em>backgroundButtonTapped</em> and <em>textButtonTapped</em>. Just use the default options when setting the outlet and actions.</p>
<p>We want to make the background button toggle the background’s color, while the text button toggles the text’s color. Inside <em>backgroundButtonTapped</em>, add the following code.</p>
<pre><code>let colorIndex = Int.random(in: 0 ... 5)

switch colorIndex {
    case 0:
        view.backgroundColor = UIColor.red;
    case 1:
        view.backgroundColor = UIColor.blue;
    case 2:
        view.backgroundColor = UIColor.yellow;
    case 3:
        view.backgroundColor = UIColor.green;
    case 4:
        view.backgroundColor = UIColor.orange;
    case 5:
        view.backgroundColor = UIColor.purple;
    default:
        view.backgroundColor = UIColor.white;
}
</code></pre>
<p>Clicking the button will give us one of 6 random colors for the background. Let’s do something similar inside <em>textButtonTapped</em>.</p>
<pre><code>let colorIndex = Int.random(in: 0 ... 5)

switch colorIndex {
    case 0:
        helloWorldLabel.textColor = UIColor.red;
    case 1:
        helloWorldLabel.textColor = UIColor.blue;
    case 2:
        helloWorldLabel.textColor = UIColor.yellow;
    case 3:
        helloWorldLabel.textColor = UIColor.green;
    case 4:
        helloWorldLabel.textColor = UIColor.orange;
    case 5:
        helloWorldLabel.textColor = UIColor.purple;
    default:
        helloWorldLabel.textColor = UIColor.black;
}
</code></pre>
<p>Give the app a run. You’ll find the you can shift your focus from one button to the other by sliding your thumb left and right on the the remote&#8217;s Touch Surface. You can then tap the Touch Surface to press the button. Perhaps you’ll also find that the blue background with the green text looks pretty nice!<span class="Apple-converted-space"> </span></p>
<h2>Gesture Recognizers</h2>
<p>You’ve got a good feel for the basic user interaction now. Sliding around the focus and tapping buttons is the key to most menus. That Siri Remote has some other buttons on it though. We should make them do something in our app! If we want to harness the action of the Play/Pause button, we can use a tap gesture recognizer.</p>
<p>Add some tap gesture recognizer code in <em>viewDidLoad</em> inside your <em>ViewController.swift</em> file.</p>
<pre><code>let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
tapRecognizer.allowedPressTypes = [NSNumber(value: UIPress.PressType.playPause.rawValue)];
self.view.addGestureRecognizer(tapRecognizer)
</code></pre>
<p>Now we need to define the function tapped down at the bottom of <em>ViewController.swift</em>.</p>
<pre><code>@objc func tapped(gestureRecognizer: UITapGestureRecognizer) {
    view.backgroundColor = UIColor.white;
    helloWorldLabel.textColor = UIColor.black;
}
</code></pre>
<p>Whenever the user taps the Play/Pause button, we will reset the background to white and the text to black. Run the app and give it a try!</p>
<h2>Conclusion</h2>
<p>Today we made a Hello World app for Apple TV and learned how to handle basic user interaction. Most of the tools available to you in iOS are also available in tvOS. Now that we’ve demystified the user interaction a bit, you should be off to the races! Look out for more Apple TV tutorials in the future. Until then, thanks for reading the HangZone blog!</p>
<p>The post <a href="https://hangzone.com/how-to-make-your-first-apple-tv-app/">How to Make Your First Apple TV App</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>The Business Strategy of Collectibles in Smash Bros Ultimate</title>
		<link>https://hangzone.com/the-business-strategy-of-collectibles-in-smash-bros-ultimate/</link>
		
		<dc:creator><![CDATA[Tyler Bandy]]></dc:creator>
		<pubDate>Mon, 31 Dec 2018 18:05:25 +0000</pubDate>
				<category><![CDATA[Game Industry]]></category>
		<guid isPermaLink="false">https://hangzone.com/?p=1948</guid>

					<description><![CDATA[<p>I just got Super Smash Bros Ultimate for the Nintendo Switch. The controls feel great, the stages look incredible, and the cast of characters is amazingly large. The Smash Bros ...</p>
<p>The post <a href="https://hangzone.com/the-business-strategy-of-collectibles-in-smash-bros-ultimate/">The Business Strategy of Collectibles in Smash Bros Ultimate</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>I just got Super Smash Bros Ultimate for the Nintendo Switch. The controls feel great, the stages look incredible, and the cast of characters is amazingly large. The Smash Bros series has always had a lot of collectibles—there were 190 trophies in Melee, for instance. Smash Bros Ultimate raised the bar a bit with its new collectibles called Spirits. They boost your fighters’ stats and abilities. They’re also just fun to collect! And here’s the kicker. There are 1,300 of them! So far! And they just put out three a couple days ago that are only available for a week!</p>
<p>If this massive, growing volume of collectibles, coupled with limited time windows, reminds you of something you’d see in a mobile game, we’re on the same page. Let’s dig into why Nintendo is giving this a shot.</p>
<h2>Traditional Collectibles in Console Games</h2>
<p>There’s something magical about collectible items. A huge subset of the gaming population thrives on the satisfaction of completion, even if there’s not a special prize at the end of the road. Pokemon is perhaps the most famous collection series of all time. If you catch all 150 Pokemon (excluding Mew) in the original Red and Blue, you get an in-game diploma. That’s it! Most of those last dozen hours or so didn’t net you any Pokemon that are actually useful for your team. They only served as collectibles—“box trophies” to use the familiar jargon of the mobile gaming world. Nevertheless, I completed my Pokedex in Pokemon Red, and I know plenty of other people who got that diploma too.</p>
<p>I logged a lot of hours in Pokemon, but I quit after I caught the last one. That was fine with me. I had plenty of fun hours playing the game. It was fine with Nintendo too. They didn’t stand to make any more money from me playing the game any longer. I’m sure they were ready for me to move to the next game.</p>
<p>For whatever reason, though, I never picked up Pokemon Gold or Silver. I never checked out any of the later major entries in the series either. Sure, I road the nostalgia wave and played Pokemon Go out in the streets with everyone, but that was a fairly short diversion. I don’t have a good reason for why I never got another Pokemon game. I enjoyed the original, and the reviews have been positive on most of the later entries. I just didn’t pull the trigger on the next title.</p>
<h2>Collectibles in Mobile Games</h2>
<p>Publishers understand that there’s always going to be some slippage of previous users from one title to its sequel. Fortunately for mobile game publishers, they have an ace up the sleeve: in-app purchases.</p>
<p>Let’s talk about this phenomenon with respect to DragonVale, one of the pioneers of collecting style games on mobile. Back in 2011, Backflip Studios launched the original dragon breeding game (at least I recall it being the first) in the App Store. The entire game had no purpose other than to collect dragons. There was no battling or any reason you might chase after the rare double rainbow dragon, other than to complete your collection. I played the game, and eventually I collected them all.</p>
<p>Here’s the twist. I didn’t quit playing after I got that Game Center achievement. They kept putting out new dragons, and I kept collecting them. I eventually quit, but if they kept putting out content faster, maybe I’d still be playing. Many people still are.</p>
<p>I’ve written about GungHo’s Puzzle and Dragons on this blog in the past. It’s the mobile game in which I’ve logged the most hours, other than something I’ve developed myself. That game has over 5,000 monsters right now. I remember when it had 2,000 a couple of years ago. They continue to put out a lot of content, and while the game doesn’t generate as much money as it did five years ago, it’s still one of the top earning games in Japan in 2018. Backflip and GungHo are certainly earning more on these seven year old games than they are on their newer titles.</p>
<h2>Limited Time Collectibles</h2>
<p>Now it’s one thing to keep adding new stuff. It kicks things up a notch when you start taking things away! Both of the games I just mentioned utilize that approach effectively. Dragonvale has seasonal dragons that can only be bred around certain holidays. Puzzle and Dragons is usually running some sort of one to two week event with limited-time monsters. Sometimes these things come back. When I played Dragonvale, they had an event called Bring ‘em Back where you could breed any previous limited time dragon…for a limited time. I can only imagine the uptick in in-app purchases! On the other hand, sometimes these limited collectibles don’t come back. There are some highly sought after monsters in Puzzle and Dragons that appeared years ago as part of collaborations involving some licensed IP that are unlikely to ever appear again.</p>
<p>The knowledge that any limited time collectible may truly be available for the last time ever takes gamers’ collector instincts to a new level. When I played Pokemon Red, I actually took a couple of years off between beating the game and finishing my collection. There was no urgency. Not so in the realm of limited time content! Players are invested in their collections. They’ve put a lot of time into them, and they’re proud of them. If they miss some limited content, that compromises their whole collection! That’s why these collection based mobile games have such amazing retention.</p>
<h2>Nintendo’s Game Plan</h2>
<p>It’s time to circle back to Nintendo. The traditional console publishers have started to adopt pricing techniques from the mobile world in recent years, most notably in-app purchases. Some companies like EA tend to get a lot of grief over this, but Nintendo has received mostly favorable feedback for offering good value for a reasonable price when it comes to add-on content. There’s already a slate of five Smash Bros DLC packs to be released in the coming months, each with an additional character and stage.</p>
<p>Since Nintendo is pursuing the DLC route more heavily, it makes sense for them to focus on retention tactics that have worked so well on mobile. After all, you need to keep as many players active as possible to maximize the DLC purchases. If all goes well for Nintendo, they can spend much less effort optimizing Smash Bros Ultimate and continuing to make a good reliable revenue stream from it, as opposed to building a new game, which may or may not have as large of an audience. Less cost and more predictable revenue are a combination just too good for the console publishers to dismiss!</p>
<h2>Concluding Thoughts</h2>
<p>I’ve already got about 200 spirits, plus two of the three limited time ones. You can bet I’m going to have the remaining limited one before time expires. At that point, I’m going to be somewhat invested in my collection. I’ll have to get the next set of limited time items when they come out, or else I would be devaluing my previous collecting efforts by ruining my overall collection’s potential to ever be complete. Since I’ll be playing the game, I might as well pick-up any new in-app purchases that come out along the way. As long as Nintendo keeps delivering good value, I’m happy with that arrangement.</p>
<p>The post <a href="https://hangzone.com/the-business-strategy-of-collectibles-in-smash-bros-ultimate/">The Business Strategy of Collectibles in Smash Bros Ultimate</a> appeared first on <a href="https://hangzone.com">HangZone</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
