Implementing preferences in iOS – the right way

Posted in Computer Science, Morse Trainer on August 25th, 2013 by michael

Douglas_Groce_CorriganA few weeks ago, I shared how I implemented preferences in my Morse Trainer appthe wrong way. Since then, I’ve learned, and I present here a brief synopsis on how to do preferences the right way.

I’m getting ready to update the app. I’ll be taking out the tabbed view and giving access to the preferences/about page via a button. While I expect the Settings page itself to look pretty much the same as before, the insternal storage location of the two settings – code sending speed and text source – will move from a user-space simple file to the User Defaults system. In the transition, the code will get a bit less complex.

Looking back at the old code, I see it’s not really as bad as I thought. At least I’m using using a keyed archive data structure. Let’s see how we can clean this up a bit, though.

Here’s how we’d rewrite last time’s code snippets.

What we did before:

- (void)viewWillAppear:(BOOL)animated {
   [super viewWillAppear:animated];
   .
   .
   .
   // Get the current stored preferences
   // Get the full path of our preferences data archive file
   NSString *prefsDataPath = [self preferencesDataPath];
   // Attempt to unarchive it
   PreferencesData *preferencesData = [NSKeyedUnarchiver
   unarchiveObjectWithFile:prefsDataPath];
   // If it didn't exist, set it to defaults
   if (!preferencesData)
   {
      // Set to the default data
      sendSpeed = 2; // fast
      textSource = 0; // quotes
   }
   else
   {
      sendSpeed = [preferencesData sendSpeed];
      textSource = [preferencesData textSource];
   }
   .
   .
   .
}

And what we do now:

- (void)viewWillAppear:(BOOL)animated {
   [super viewWillAppear:animated];
   .
   .
   .
   sendSpeed = [[NSUserDefaults standardUserDefaults] stringForKey:@"sendSpeed"];
   textSource = [[NSUserDefaults standardUserDefaults] stringForKey:@"textSource"]
   // If there's not already data there, the system will return nil, so we set it here
   if (sendSpeed == nil)
      sendSpeed = 2; // fast
   if (textSource == nil)
      textSource = 0; // quotes
   .
   .
   .
}

When it’s time to save, here’s what we did:

-(IBAction) sourceControlIndexChanged {
   textSource = self.sourceControl.selectedSegmentIndex;
   // Save the current preferences data to disk
   PreferencesData *preferencesData = [[PreferencesData alloc] init];
   [preferencesData setTextSource:textSource];
   [preferencesData setSendSpeed:sendSpeed];
   // Get the full path of our preferences data archive file
   NSString *prefsDataPath = [self preferencesDataPath];
   // Archive the preferences data to file
   [NSKeyedArchiver archiveRootObject:preferencesData toFile:prefsDataPath];
   [preferencesData release];
}

And here’s what we do now:

-(IBAction) sourceControlIndexChanged {
   [[NSUserDefaults standardUserDefaults] setValue:self.sendSpeed forKey:@"sendSpeed"];
   [[NSUserDefaults standardUserDefaults] setValue:self.textSource forKey:@"textSource"];
   [[NSUserDefaults standardUserDefaults] synchronize];
}

So we’ve saved a lot of code and increased understandability, and all by doing it “the right way.”

What have you done “the wrong way” that you’d like to go back and do over?

Photograph of Douglas “Wrong Way” Corrigan courtesy of Wikipedia and sourced from the U.S. Government; as such, it is in the public domain.  I highly recommend the very entertaining Wikipedia article.

Implementing preferences in iOS – the wrong way

Posted in Computer Science, Morse Trainer on July 13th, 2013 by michael

1280px-PAVE_Paws_Computer_Room

It’s not really my fault.  Okay, it is my fault, but I have an excuse.  I was a beginning iOS programmer.  How was I supposed to know?

I’ve been developing software for money since 1985.  In college, we used an IBM 360 mainframe, segued gradually to “mini” computers – a variety of DEC VAXes – and finally got a bit of exposure to the original IBM PC, running MS-DOS.  We learned FORTRAN, Ada, Pascal, PL/1, Prolog, C, and assembly language and had a tiny bit of exposure to Unix.

When I got to industry, it was back to the mainframe.  I developed flight software for the Atlas launch vehicle and its Centaur upper stage.  It was programmed in assembly language and hosted on a CDC Cyber mainframe running a cross-assembler written by us (in FORTRAN!).  The Cyber also hosted our written-in-house linker/loader/simulator.  It was a pretty slick setup for the time, in spite of non-interactivity of the tools.  We spent a lot of time waiting for printouts.  But there were no punchcards!  Unlike the college mainframe.

As the years went by, we graduated to VAXes and high-level languages (Ada!) and then eventually to Sun workstations and other high-level languages (C++!).  And the flight computers became smaller, lighter, and faster as time went on.  Although space-qualified computers are never going to be particularly small, light, or fast.  My iPhone has way more power than the best flight computer I’ve ever worked on.

But there’s a common element among all those machines and my work on them.  There was no such thing as a GUI.  In all my professional rocket science work, I never wrote a single line of software for a person to use.  Quite a handicap as I transitioned to iOS programming.

So it wasn’t entirely my fault when I wrote my first iOS preferences view.  How do you move from your app’s main view to a preferences view, change defaults, and have them apply to the main view when you get back, and do all this while following the sacred object-oriented religion, not sharing any global data between objects, and making preferences persist between executions of the app?  I finally figured it out –  but I had no idea a couple years ago when I wrote my first app.  But I worked something out.

So how do you do it the wrong way?  The file system.  Every time the Morse Trainer app is launched, it checks for the existence of its preferences file.  If it doesn’t exist, it creates it and puts in default values for its two preferences.  It goes through the same routine when transferring from another view (in this case, the only other view is the preferences setter), ensuring that it always knows the user’s current preferences.  Behold:

- (void)viewWillAppear:(BOOL)animated {
 [super viewWillAppear:animated];
 .
 .
 .
 // Get the current stored preferences
 // Get the full path of our preferences data archive file
 NSString *prefsDataPath = [self preferencesDataPath];
 // Attempt to unarchive it
 PreferencesData *preferencesData = [NSKeyedUnarchiver
   unarchiveObjectWithFile:prefsDataPath];
 // If it didn't exist, set it to defaults
 if (!preferencesData)
 {
    // Set to the default data
    sendSpeed = 2; // fast
    textSource = 0; // quotes
  }
  else
  {
    sendSpeed = [preferencesData sendSpeed];
    textSource = [preferencesData textSource];
  }
  .
  .
  .
}

Pretty much the same thing happens in the settings controller, except that all changes are written to disk immediately, just in case the view is swapped out:

-(IBAction) sourceControlIndexChanged
{
  textSource = self.sourceControl.selectedSegmentIndex;

  // Save the current preferences data to disk
  PreferencesData *preferencesData = [[PreferencesData alloc] init];
  [preferencesData setTextSource:textSource];
  [preferencesData setSendSpeed:sendSpeed];

  // Get the full path of our preferences data archive file
  NSString *prefsDataPath = [self preferencesDataPath];
  // Archive the preferences data to file
  [NSKeyedArchiver archiveRootObject:preferencesData
    toFile:prefsDataPath];
  [preferencesData release];
}

And how did I implement the Settings view?  A tab view controller.  Sheesh.  I don’t think I could have done it any more awkwardly.  All I needed to do was push the Settings view controller via a button or something and pop it back off when I was done with it.  But that never occurred to me.  And the Apple reviewers let me get away with it, so I guess it wasn’t too terribly abnormal.

I didn’t know about passing information back and forth between controllers (prepareForSegue in the forward direction and delegation for going back).  So I guess I did the best I could at the time.

I’m about to start a rewrite of the app to add a whole bunch of new content – see this post for a partial shopping list – and I’ll redo both the data storage/retrieval setup as well as how the inter-view navigation takes place.  Watch this space for details on how I’ll do it The Right Way.

Thanks for the suggestions

Posted in Morse Trainer on June 1st, 2013 by michael

Persons_throwing_stones_at_the_telegraphs_-_sign

Many thanks to the thousands of people who have downloaded the Morse Trainer app, and especially for the suggestions I’ve received to improve it.  I had planned to release another update by now, but my wife’s extended illness meant that I needed to concentrate on supporting her for a few months.  She’s slowly returning to health now, so I’m back to work.  Watch for an update with many of your suggestions incorporated in the near future.

Photo by Andy Mabbett and released under the Creative Commons Attribution-Share Alike 3.0 Unported license.

Thoughts on the next version of Morse Trainer

Posted in Morse Trainer on February 19th, 2013 by michael

iTunes icon 1024x1024

I’m delighted to note that Morse Trainer has become popular with a few hard-core Morse code aficionados!  I pretty much created it just for fun and because there didn’t seem to be anything else quite like it.  The App Store reports that there have been 2,286 downloads in the past six months, and its popularity seems to be picking up.  It’s gotten a few good reviews as well – thanks to all who have taken the time to write something, good or bad.

A few users have written me asking for some changes/upgrades.  Here’s what’s been requested so far:

  • Separate the app’s volume control from the system’s ringer volume (this seems to be people’s biggest issue – they mute their ringer or otherwise disable ringer volume control, which is what the app uses for its volume setting, and the app doesn’t beep, which is the whole point of the app!)
  • Add optional punctuation
  • Add an option to use light instead of sound
  • Add faster sending speeds or more fine-grained control of speeds
  • Add the ability to repeat the same message multiple times before revealing the text
  • Add the ability to store messages for use when no data connection is available

Most of those things are possible and I’m trying to decide which ones would have the most impact without making the app too complex.

What do you think?  Which of these would you find useful and which would just add unneeded complexity?  Are there other changes you’d like to see?

Morse Trainer – Words per Minute

Posted in Morse Trainer on February 7th, 2013 by michael

Morse Trainer Icon

I’ve had a few questions lately as to the sending speeds in Words per Minute (WPM) of the various settings in Morse Trainer.  Here’s the lowdown:

Slow – 5 WPM

Medium – 12 WPM

Fast – 18 WPM

Extra Fast – 20 WPM

One or two people said they believe the Slow speed is too slow to be useful and they would like me to add even faster sending speeds.  What do you think?

I appreciate the kind feedback the app is getting.  I actually have a few fans!

Emergency Update – Morse Trainer v.2.1 is Coming Soon!

Posted in Morse Trainer on November 19th, 2012 by michael

 

A kind user named Pedro let me know today that Morse Trainer’s new Quotations section wasn’t working.  Thanks, Pedro!

I confirmed the problem.  It turns out the website the app uses to find the quotes had made some changes and I needed to change Morse Trainer to match.  The changes were made in just a few minutes and version 2.1 of the app has been submitted to the App Store for approval.  Let’s hope the process moves as quickly as possible.  I absolutely hate having a broken app out there.

Many thanks to everybody who has downloaded and enjoyed the Morse Trainer app.  Help is on the way!

Morse Trainer 2.0 is available for download!

Posted in Morse Trainer on October 26th, 2012 by michael

Samuel Morse thanks you.  And I thank you.

Download or upgrade your copy of the Morse Trainer App, now featuring:

  • Universal app – now looks great on your iPad as well as your iPhone
  • iPhone 5 compatible
  • Broken Twitter link replaced with random quotations
  • Pretty new retina graphics

Still at the same low, low price of Free!

Morse photo: Louis Jacques Mandé Daguerre / Macbeth Gallery Records, Archives of American Art, Smithsonian Institution

Morse Trainer 2.0 is coming!

Posted in Morse Trainer on October 22nd, 2012 by michael

 

 

That’s right, I submitted Morse Trainer 2.0 this evening.  It’s finally a Universal app (iPhone and iPad, as seen above!), has Retina-compatible graphics, and will fit on your iPhone 5.  Just what my many satisfied users have been clamoring for.  And it’s absolutely free.  No advertising, no in-app purchases.  Free.

Watch this space for its release date.

1,005 downloads!

Posted in Morse Coder, Morse Trainer on April 26th, 2011 by michael

Great news (for me, that is)!  I’ve gone over 1,000 total downloads – 1,005 to be exact.  That’s 632 copies of Morse Coder and 373 copies of Morse Trainer.  Thanks to all the loyal Morse Code fans who have downloaded my apps.

861 downloads!

Posted in Morse Coder, Morse Trainer on April 10th, 2011 by michael

That’s right, I now have 861 downloads, of which 599 are Morse Coder and 262 are Morse Trainer! Pretty cool

In fact, a quick perusal of the App Store reveals a four-star review of Morse Trainer. My adoring public would like to see the sending speed increased even more. I’ll have to work on that.