Mark Rickert

Digital Nomad

Opening a Modal View Controller From a UITabBarController

I recently had to implement a workflow for a client whereby they had a UITabBarController and tapping one of the tabs would open a modal view controller.

This seemed obvious to implement. Just add the tabBarController(tabBarController, shouldSelectViewController:viewController) method in my tab bar controller’s delegate and return false, then open the modal!

If you try that, your app will crash and you will get an error message:

1
2
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: 'Application tried to present modally an active controller <TabBarController>'

Here’s the problem. UITabBarControllers don’t have a UINavigationController stack. Modal view controllers have to be presented from a nav controller so presenting from the tab bar doesn’t work. At all.

So let’s figure this out.

Instead of UITabBarController -> ... lets have a UINavigationController -> UIViewController -> UITabBarController -> ... and then we can present the modal from the original nav controller.

This example will be using the awesome RubyMotion gem ProMotion.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class AppDelegate < PM::Delegate
  def on_load(app, options)
    # Make sure the navigation controller is created but the
    # nav bar is hidden immediately
    open DummyScreen.new(nav_bar: true, hide_nav_bar:true)
  end
end

class DummyScreen < PM::Screen
  # You have to do this in will_appear for the open method to work
  def will_appear
    tbc = PM::TabBarController.new(
      Screen1.new(nav_bar:true),
      Screen2.new(nav_bar: true),
      tappable_placeholder_screen,
      Screen4.new(nav_bar: true)
    )
    tbc.delegate = self

    # Make sure this is pushed onto the nav stack without animation
    open tbc, animated:false
  end

  def tabBarController(tabBarController, shouldSelectViewController:viewController)
    if viewController == tappable_placeholder_screen
      # This is opened on the main nav controller, not the UITabBarController
      open_modal MyModalScreen.new(nav_bar: true)
      false
    else
      true
    end
  end

  def tappable_placeholder_screen
    @_tappable_placeholder_screen ||= TappablePlaceholderScreen.new
  end
end

class TappablePlaceholderScreen < PM::Screen
  title "Placeholder"
  tab_bar_item title: "Placeholder", item: "icon_placeholder"
end

So that’s how you can show a modal view controller and make a tab bar item tap invoke it!

Creating Your Own Stylers for UIControl Subclasses in RMQ

I love using RMQ. It makes it so easy to initialize, append, and style views you add to your iOS app’s hierarchy. The styling subsystem is robust and elegant. But what to do when you have a custom class that you want to be able to style?

Having recently ran into this problem and solved it, it’s actually pretty easy.

I wanted to implement DCRoundSwitch in my app and style it with RMQ. DCRoundSwitch is a UIControl subclass, but it needed a custom styler defined to be able to style it in my controller’s stylesheet.

Adding the class cocoapod

First of all, add pod 'DCRoundSwitch' to your Rakefile and run rake pod:install. This allows you to use the component in your app.

Adding the switch to the view

Inside the viewDidLoad method (or if you’re using RedPotion or ProMotion, like me: on_load), add the following code to put the switch on your view:

1
2
3
rmq.append(DCRoundSwitch, :my_switch).on(:tap) do |sender|
  # Do something when the switch is switched on or off
end

Adding the custom styler

Here’s where the fun part comes in. I looked at the .h file for DCRoundSwitch and found the following properties:

1
2
3
4
@property (nonatomic, retain) UIColor *onTintColor;
@property (nonatomic, getter=isOn) BOOL on;
@property (nonatomic, copy) NSString *onText;
@property (nonatomic, copy) NSString *offText;

Translating those into an RMQ styler looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
module RubyMotionQuery
  module Stylers
    # Don't forget to inherit from UIControlStyler
    # to get all the other UIControl goodness
    class DCRoundSwitchStyler < UIControlStyler

      def on_tint=(color)
        @view.onTintColor = color
      end
      def on_tint ; @view.onTintColor ; end ;

      def on_text=(text)
        @view.onText = text
      end
      def on_text ; @view.onText ; end ;

      def off_text=(text)
        @view.offText = text
      end
      def off_text ; @view.offText ; end ;

      def on=(on)
        @view.setOn(on, animated:true)
      end
      def on? ; @view.isOn ; end ;

    end
  end
end

If you try and use these stylers now, RMQ will complain and then promptly crash with:

my_stylesheet.rb:12:in ‘my_switch:’: undefined method ‘on_tint=’ for #<RubyMotionQuery::Stylers::UIControlStyler:0x119597ce0 …> (NoMethodError)

As you can see, it thinks we’re using a UIControlStyler so we have to tell RMQ to use our new styler instead of the standard one for UIControl. We add a method to DCRoundSwitch called rmq_styler that tells RMQ what styler to use for this class:

1
2
3
4
5
class DCRoundSwitch
  def rmq_styler
    RubyMotionQuery::Stylers::DCRoundSwitchStyler.new(self)
  end
end

Now in your stylesheet, you can create a style for your new switch:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyScreenStylesheet < ApplicationStylesheet
  def my_switch(st)
    st.frame = {
      t: 10,
      l: 10,
      w: 200,
      h: 20
    }
    st.on_tint = color.green
    st.on_text = "My Custom On Text"
    st.off_text = "My Custom Off Text"
  end
end

And there you have it! A custom styler for a UIControl subclass, but really, it could be used for any object you’d like!

Questions and Answers

People seem to be endlessly fascinated by my lifestyle and with endless fascination comes a myriad of questions. Maybe it’s because they don’t understand the lifestyle. Maybe they envy it. Others ask questions because they’re in the process of transitioning to a lifestyle on the road like mine.

This brings me to Carol. She is a family member of a coworker and has been thinking about retiring to live in a van and travel the country. She has a lot of questions so instead of answering them privately, I’m going to take the opportunity to answer them here on my site for the benefit of everyone!

How do you find good places to park? Do you have problems with it? If so what kind?

I typically live at airports but when I’m traveling I find places to park using FreeCampSites.net. I can be fairly stealthy and I’ve stayed in the Marina District of San Francisco for about 4-5 days with no problems overnight. I also stayed on the streets of Denver for a while too. Oddly enough, I’ve had the most issues with other campers on BLM land out in the middle of nowhere. I once had two guys shooting their rifles in the camp. I went over there and politely asked them to not discharge their weapons in the vicinity of other people and they stopped. I travel with a firearm as well, but I’m smart enough to know not to discharge it around other people unless my life is in danger.

What about showers?

My RoadTrek has a shower built in (with a 6 gallon water heater), but I rarely use it. I live mostly at airports (skydiving lifestyle) and most dropzones have showers and laundry facilities for their patrons. These facilities are only for skydivers so you wouldn’t be able to just park at a dropzone and take a shower. We can spot non-skydivers a mile away :)

I like the way the RoadTreks are laid out. How long have you been living in yours?

I set out on the road on August 15th, 2014. I sold everything I owned (or donated it) and locked the keys to my apartment inside for my landlord. I sold my car the day before and my motorcycle a few weeks before that. So at the time of this writing, I’ve been living a nomadic lifestyle for about 5 months and I’m loving it! I’ve got no plans to going back to house or apartment dwelling at this time.

Do you have moisture problems when it is real cold out or does your propane heater dry out the air enough while you have it on?

I use Reflectix in most of my windows for insulation and blocking light. It’s a great product and I know a lot of other RVers who use it. The only moisture problems I have is in the mornings there’s moisture between the Reflectix and the rear window (presumably from me breathing all night so close to it). Other than that, the heater does a good job of drying out the van… sometimes it does too good of a job so I keep a bottle of water by me at night that I can drink from so I’m not too dehydrated in the morning.

What is your heating cost? Spend much time North or in cold weather?

I have a 5 gallon propane tank on board that costs about $20 to fill. I’ve filled it about 5 times, so about once a month. This propane also runs my refrigerator. I try and avoid cold weather, mostly because I don’t want to have to winterize my water and sewage tanks. I try and stay in climates where it never freezes, though I got close a few times when I was in Arizona, and Florida has a cold snap going on right now that’s causing me to use more heat than normal.

Online I found some pads for under a mattress which helps the air circulate so no mold grows under it. What about those?

I haven’t had a problem with mold, but I generally am moving my mattresses around every week or so since the rear of my van is convertible from a bed to a living room space with a table. I have a foam twin mattress pad I bought at Walmart for $20 that works great.

I am wondering if you have a home base and how you handle having a ground tethered address for stuff like insurance, taxes, health care and job applications?

I don’t have a home base, but I do have an official home address in Florida. Why Florida? Well, I grew up there so I have ties to it, there’s no state income tax on individuals or pass-thru LLCs, and I plan to spend time there during the winter (as I’m doing now) which is important in case my state of residency is ever questioned.

I utilize a service called St. Brendan’s Isle for my mail. It’s like a post office box, but they give you a physical address and help you establish residency in Florida if you need to. It’s a service specifically for full time mariners and RVers. They receive my mail and scan in the envelope so I can view it online. Then I can say, “shred it”, “hold it”, “send it to me wherever I am”, or “open it and scan it”. I use that last option more often than the others and I get almost zero mail sent to my current location because I can read everything online!

As far as my insurance, health care, and taxes are concerned, my permanent address is in Green Cove Springs, FL. As far as job applications go… I don’t fill out any because I own and run my own business from the road. The business’s registered address is that same post mail box in Green Cove Springs.

Insurance is a funny thing. My van is technically considered an RV because it has a bathroom and a kitchen. Because I don’t have another vehicle and I live in the RV, many insurance companies I contacted wouldn’t cover me. I finally found a policy with Progressive for about $800/yr that covers full-time RVers. If you keep a sticks-n-bricks home or another vehicle, you probably won’t have trouble finding decent coverage on the vehicle.

Do you have a storage space anywhere for things you might use sporadically, but do not want to carry with you all the time? Sounds like you don’t. I would hate to give up my sewing machine but I often don’t use it for months at a time.

I have a banker’s box with old tax records at my parents’ house in Virginia along with two framed photographs I wanted to keep. That’s it. I wrote in a previous article about how getting rid of everything but the essential was psychologically freeing for me and I still believe that. I want to live a minimalist lifestyle and I can’t do that with a bunch of stuff.

Sometimes I miss having things for the kitchen (since I love to cook), but then I remember why I sold everything… to live simply. Once I have the desire to start accumulating a bunch of stuff again, I’ll probably go back to an apartment. I do know people with larger RVs that keep lots of stuff in them. A Class C RV could easily accommodate room for a sewing machine and all the things that go with it.

Homeless by Choice

I’m homeless. By choice.

Well… I’m houseless. My home is a Roadtrek ‘98 Dodge Ram van converted to a motorhome by some fine folks in Canada.

Some may find this lifestyle off-putting, but I’ve joined the ranks of an estimated quarter of a million Americans living full time on the road. It was a conscious decision I made in early 2014 after thinking about it for a while. It took a few months to put my plan into action and I officially left my sticks-and-bricks apartment in the trendy neighborhood of Plaza Midwood, Charlotte, NC on August 15th, 2014.

I had to sell or donate an entire apartment’s worth of stuff I’d accumulated over the years. I’m not sentimental and don’t get attached to physical things. So parting with all that stuff was easy for me emotionally. It was actually quite freeing once I realized how much my stuff really owned me and not the other way around. Some of the bigger items like my car and motorcycle were harder to unload than household items and I wound up selling my car the day before I left to go on the road!

What about work?

Some people ask me how I can afford to live like this… jumping from family member to National Park and back again. I’m a software consultant and manage my own iPhone app business. Consulting gives me the flexibility to travel and work whenever I find a nice place to stop.

My typical routine is to find a place I can set down roots for a few days like a free campsite in a National Park or my myriad of family members scattered across the United States (I’m currently visiting my sister and her family in Los Angeles). Then I work like crazy for a few days–putting in 10-12 hour days and taking breaks to hike for an hour or go swimming in a nearby body of water. Then when I’m ready to move on, I plan my destination, route, and take off.

I’m currently working about 30 hours a week on the road. I’m completely debt free and I work because I love what I do, not because I need the money.

Challenges of the road

This lifestyle isn’t for the faint of heart.

On the downside, you’re never in the same place. On the upside, you’re never in the same place!

Silliness that homeowners have to think about like cutting the grass or watering the plants or waiting around for the cable guy to show up are concerns of the past. I’m happy to be free of the concerns of both homeownership and apartment life. But living on the road comes with a whole different set of challenges that require both planning ahead an ingenuity.

Internet Connection

Lots of articles have been written about full-time RVing and internet access. I have a 20gb Verizon cellular data-only plan for $100/month. It’s a great deal and my primary source of internet.

But that means I really have to watch my bandwidth usage. No more frivolous youtube videos or Facebook photo browsing. This has actually done wonders for my productivity. But for the youtube videos I do need to watch, there’s a handy setting that allows you to default to the lowest quality.

I supplement this 20gb with free WiFi hotspots I find at places like McDonalds and Starbucks and it’s totally sufficient for my work week and multiple video conference calls with work. I just set my Google Hangout quality setting to “low” (~30kbps) or “audio only” (~8kbps).

My RV is equipped with a Cradlepoint MBR95 router that serves my cellular internet connection to all my wireless devices and is able to connect to WiFi hotspots and use that as an internet source when available. It’s a pretty nifty device to have and serves as my central internet hub, using whatever internet source that is available.

Where to sleep?

Sleeping isn’t a problem when I’m in a family members’ driveway. When I’m on the road, it’s a challenge. Yeah, I could sleep at a Walmart or Cracker Barrel or automotive parts store (all known friendly places for RVers to sleep overnight). But what’s the fun in that? It’ll be safe but also loud and bright.

I prefer finding some public land to sleep on. It’s almost always off the beaten path, but usually worth it in terms of view and solitude. I spent a night in the panhandle of Texas one night where I was able to see the Milky Way Galaxy clearer than I’d ever seen it before. The 40 minutes down a dirt road to find the land was worth it.

I’ve stayed at RV parks a few times but try to stay away from places I have to pay to sleep. Dumping my RV’s tanks costs about $20 and a night at an RV park is usually $30-$40. It’s really a minimal cost for the electricity on top of getting to dump my tanks. I only do this when my tanks are full and typically only stay one night.

Long hours of driving

Sometimes it isn’t practical, but I try and live by the “320 rule”: I stop driving when I get to 320 miles or 3:20pm. Whichever comes first.

Every once in a while, I find myself driving all day. It could be because I’m on a schedule to get to an event or just because I miscalculated the time it would take to get to my next destination.

There’s no avoiding driving long distances when you want to see as much of the country as I do at this point in time. It helps that I avoid highways like the plague. Unless I’m on a time crunch, I have all my GPS devices to “avoid highways”. That way, I get to see America in all it’s glory… the small towns and byways. Highways and interstates always bypass the real America and the scenic routes. I want to see all the small towns and experience the scenic routes that highlight the beauty of this great country. You’ll never get that by taking interstates from place to place.

Electricity & Water

Luckily, my RV has an onboard 2.8kw generator so I can make my own electricity. When I’m working and boondocking (or “dry camping” as some call it - camping with no water/sewer/electricity hookups), I typically run the generator for an hour at lunch and an hour in the evenings. This is simply because my battery bank doesn’t have the amperage needed charge my laptop from the power inverter. I’ve been able to hook up to electricity at most dropzones and at all family member houses… though a few days ago, someone called the cops because they thought I was stealing electricity when I was parked on the street in front of a relative’s house. Seriously? Like they wouldn’t have noticed the bright orange cord going through their living room window.

Water isn’t really a problem because of the onboard 35 gallon fresh water tank. I just fill it up whenever I’m at a place with easy access to water. I’ve never ran into a situation where my tank was empty but I keep a few gallons of fresh bottled water in the storage compartment in case of emergencies. In retrospect, I should have emptied the tank when I drove over the rockies in Colorado–I probably would have gotten much better gas mileage.

Entertainment

I don’t own a television. Frankly, I hate television. There’s almost nothing good on it and I’d rather spend my time doing something productive instead of staring blankly at a screen just to pass the time. I have life to live!

It doesn’t really make a whole lot of sense to have a TV, antenna, and all the accessories that come with it in such a small space when the entire country is my playground.

But when I do need to relax and unwind, I have my iPad that streams videos from my Apple Time Machine hooked up to my router. I try not to stream too much from the web (see aforementioned bandwidth limitations) so I store media files on my Time Machine and stream them with a great app called Infuse. I’ve mounted my iPad with a few makeshift brackets right above the head of my bed so I can lay down comfortably and watch whatever I want.

Why did I do this?

Skydiving has profoundly changed my life since I did my first tandem skydive in November 2013. Then another one in December on my birthday where my parents came and watched. I’ve been a licensed skydiver since May 2014 and as of the date of publication I have over 130 jumps under my belt.

It probably sounds silly, but I believe we all can learn a lot about who we are and what we want in life by throwing ourselves out of an airplane and hurtling towards the earth at 120-170mph.

  • Doing things that make you feel uncomfortable is the only way to grow in life
  • Being satisfied with the status quo is not acceptable.
  • I’m ultimately responsible for me. There’s nobody else that’s going to pull my chute for me or perform the correct maneuvers to make a safe landing.
  • The reward is only as great as the risk taken.
  • Adventures only happen if you make them happen.
  • Sometimes you have to ignore the naysayers and do what makes you happy. Some people will never understand.

I decided to make an adventure happen and take a risk that made me uncomfortable.

So far this big risk has had a big payoff and it’s only been 2 months. I can’t wait to see what the next year brings!

Prepending the Default RubyMotion Build Task

UPDATE - I’ve rewritten this article with a more elegant solution.

Thank’s to the helpful Hipbyte team member Watson1978, I was able to figure out how to run tasks before and after build processes in RubyMotion. I’ve ofeen wanted to do things before building to the simulator like downloading assets into the Resources folder, but never knew how to do it. My solution was to extend the core RM build processes and alias methods, override the methods, and then run what I wanted to before running the aliased methods… yeah… annoying, huh?

It’s actually really easy to do with a little know-how and the rake-hooks gem!

First, add gem 'rake-hooks' to your Gemfile and run bundle.

Then all you have to do is use before and afterblocks in yourRakefile` like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
before :"build:simulator"
  # File and path definitions
  file_path = 'resources/file_i_want.json'
  web_path = 'http://someurl.com/file_i_want.json'

  # Check to see if the file exists
  unless File.exist?(file_path)
    # If not, download it and put into the resources directory
    require 'open-uri'
    open(file_path, 'wb') do |file|
      file << open(web_path).read
    end
  end
end

after :clean do
  puts "Deleting resource file."
  file_path = 'resources/file_i_want.json'
  File.delete(file_path) if File.exist?(file_path)
end

You can define before and after for any of the RubyMotion rake tasks. To see a list of tasks, just run rake -T but the most common ones will be:

1
2
3
4
5
rake build:device
rake build:simulator
rake spec:simulator
rake clean
rake archive:distribution

I hope this little tip helps you automate your rake tasks in RubyMotion!

One Month Living in a Van

Today marks my 1 month anniversary of living full time in a Roadtrek Class B RV (1998 Roadtrek 190 Popular). I live with all my worldly possessions in about 140 square feet and it’s amazing!

I’ve never felt so free!

Things are definitely different living on the road and in such a small place. But there’s a calming feeling waking up and looking down the aisle of my van to see my kitchen, bathroom, wardrobe, & vehicle all contained in one small space. I guess you can say I’m part of the “tiny house movement”, but my tiny house happens to be a van. I have no rent, no electricity or water bill, no mortgage payment, and because I bought the van with cash–no car payment!

So what have I been doing with this newfound freedom? Visiting family, hitting up dropzones, and boondocking on public land & national parks. I can usually find electricity at an airport while I’m skydiving so I can run the A/C and microwave without having to run my generator. Now that it’s getting a little cooler, I barely run the generator at all when I’m off the grid and solely rely on the battery for electricity (driving or running the generator recharges the batteries).

I’ve had a great time visiting a bunch of dropzones so far… Skydive East Tennessee, Adventure Skydiving Tennessee, Skydive Alabama, Gold Coast Skydivers, and an unfortunate weathered out weekend at Skydive Dallas. I’m excited that I hit 100 jumps a few weekends ago! I basically pick the next dropzone and then plan my travel time and stops during the week based on arriving at the DZ on Friday and leaving on Sunday.

Currently I’m boondocking and working at the Lake Meredith National Recreation Area just north of Amarillo, TX. I’ll spend a few days here before I move on to my next destination… wherever that might be…

Listening to User Feature Requests

I’ve gotten a lot of positive feedback on my new iOS app aloft. One user emailed me and asked if I could make the weather station screen searchable. I told him “no problem, it’ll be in the next version that ships”.

Searchable tableviews are not hard to implement (more on that in a bit), but I had left it out simply for the fact that I didn’t think users would need that feature. The app sorts the weather stations based on closeness to your device and I incorrectly assumed that users would just want to use the closest weather station.

So I got to work. Luckily I’m using the ProMotion RubyMotion framework and making a ProMotion::TableScreen searchable is not just easy–it’s trivial. It’s literally one line of code.

I wanted the search to match more than just the title and subtitle of the table cells, so I added the search_text: attribute to my table cell creation method and added the text that I wanted to be searchable.

Here’s the commit

Overall, this feature took me 2 lines of code to implement using ProMotion. How awesome is that? If you haven’t checked out ProMotion yet, give the wiki a glance and see if maybe you could use it in your next RubyMotion project.

Lesson learned? Listen to your users. Sometimes what you think they are going to want isn’t actually what they want.

Full Disclosure: I am a core contributor on the ProMotion project.

Why You Should Internationalize Your Apps

This post originally appeared in Issue #20 of the RubyMotion Dispatch weekly newsletter on March 25, 2014

Internationalization (i18n) and localization (l10n) of your apps are a great way to reach a wider audience. The App Store is a global marketplace, so why not try and cater to as many as possible?

You may be asking, “What’s the difference between i18n and l10n?” I18n is the process of making your app localizeable. Theoretically you should only have to do this once. L10n is done multiple times, once for each locale you want to support.

The i18n process should include things like:

  1. Abstracting all text into an external file located in your /resources folder. Sugarcube has some abstractions that make this really easy.
  2. Making sure things like dates, times, currency values, sort order, etc. are formatted according to the user’s NSLocale.currentLocale
  3. Make sure to test languages other than your native tongue to see how they look. German words are on average 30% longer than English!Ensuring that very long text strings will fit and look good without truncation might take some time. Use UILabel’s sizeToFit method or for even easier global styling, use RMQ’s UILabelStyler,

Once you’ve internationalized your app, you do multiple localizations into other locales. This is mostly just string translation. You can get volunteers or use a service like PhraseApp to translate your app (who conveniently have a RubyMotion Gem).

When you upload the new binary, Apple automatically detects the new languages your app is available in and denotes that in the left column of your app’s App Store page. I18n and l10n are certainly not easy, but the non-English speaking world will thank you for it.

To Prompt for App Reviews or Not? That Is the Question

This post originally appeared in Issue #18 of the RubyMotion Dispatch weekly newsletter on March 11, 2014

There’s a huge controversy over whether or not you should prompt users to rate your app after a certain number of uses or a predetermined user action.

I use Appirater and have experimented with releasing builds with and without it enabled. In my experience, I get zero reviews/ratings in my apps when I don’t specifically ask the user to do it. It’s unfortunate that users need a personal invitation to give feedback about an app, but in my experience it’s the only way to actually get reviews and ratings. Most users will just dismiss the dialog and move on, but those critical few who actually take the time to rate your app will make all the difference.

Ultimately, you need to decide where you fall on this issue and take what you think is the best course of action for your particular situation.

The Price Elasticity of Virtual Goods

This post originally appeared in Issue #17 of the RubyMotion Dispatch weekly newsletter on March 4, 2014

How did you decide on the price of your paid app? A good way to figure out a general price range is to show someone (not friends or family) your app and ask “how much would you pay for this?” Once you get a general price point start playing with the price in iTunes Connect to find the “sweet spot”.

The demand curve for your app changes as the price changes in an effect called price elasticity of demand. Demand will go down as your price goes up. How much demand changes depends on a number of factors including similar app availability (and price) and how necessary the app is to a user. Since the supply of digital goods is technically unlimited, the only thing we can do to measure change in demand is simply change the price.

Once you’ve played with the price of your app a bit, you’ll be able to find that sweet spot of income per download vs. demand of the app to reach the maximum downloads at the highest price. This is where you should keep the price of your app. Remember: not all apps need to be or should be $0.99.