Convert Trunk Notes To Plain Markdown

This shell script converts one or more Trunk Notes files to plain markdown. It removes the first 7 lines and touches the modification date to match the Timestamp line. It does not convert the markdown itself. For example it does not convert ~~ (strikethrough).

It assumes the input files all have the extension .markdown and writes files with extension .md, thus not destroying your original files.

#!/bin/sh

# Converts one or more Trunk Notes to plain markdown

for file in $* ; do
    basename=$(basename $file .markdown)
    timestamp=$(egrep ^Timestamp: $file | tr -d "A-Za-z: +-" | cut -c -14)
    sed -e '1,7d' < $file > $basename.md
    touch -t ${timestamp:0:12}.${timestamp:12:14} $basename.md
done
Advertisements
Posted in Tools | Tagged , | Leave a comment

Let’s Build a Xib-Free iOS App with C#

Why would I want to build a xib-free iOS app with C#?

  • Because I can
  • Because it’ll be fun
  • Because it’ll be less painful than using Interface Builder, storyboards, and Objective-C

Now that you know some of my biases, let’s get started:

Xamarin already has a good introduction to building an iOS application in code, so go do that now. I suggest that you use the Starter (free) edition of Xamarin Studio instead of Visual Studio. This whole series will be based on using Xamarin Studio instead of Visual Studio. When you come back, we’ll start building a slightly larger app over a series of blog articles.

We’re going to build a current weather clock. The user story is:

I would like an iOS app that shows current time and weather conditions so that I don’t have to look out the window to see the weather.

Roadmap for building our app:

  • Clock
  • Timers
  • Add an iAd banner (you do want to monetize this app, don’t you?)
  • Landscape and auto layout
  • Get current location
  • NuGet
  • Get current weather
  • Profit!

That’s enough for today. I figure you spent an hour or two on downloading and installing Xamarin.iOS and building your first xib-free iOS app in C#. We’ll start building the current weather clock next.

Posted in iPhone Dev | Tagged , , , | Leave a comment

Prepare Your iPhone for Sale

You just bought that shiny new iPhone 5 and told your spouse that you can sell your old iPhone to pay for the new one. I won’t get into the middle of a spousal argument, but I will help you prepare your iPhone for sale.

The goals of this procedure are to:

  • Preserve everything on your iPhone so you can restore it to the new iPhone,
  • Remove your personal information from the iPhone, and
  • Prepare your iPhone so it’s new owner can set it up.

(I assume that you don’t wish to unlock or jailbreak your phone.)

Step 1: Backup your iPhone to iCloud: The easiest way to preserve everything and restore it to your new iPhone is to backup to iCloud.

On the iPhone, go to Settings, iCloud. If you’re not presently signed into iCloud, it will ask you to Sign In. If you share an Apple ID with someone else in your family for iTunes, that’s great, but you probably don’t want to share an iCloud account. You only get 5Gb; that’s just about enough to backup two devices like an iPhone and iPad. You can pay for extra space if you prefer to share accounts.

If you want or need a separate Apple ID for iCloud, tap Get a Free Apple ID.

When you’re ready to backup your iPhone, connect it to a WiFi network, go to Settings, iCloud and scroll to the bottom. Tap Storage & Backup. Enable iCloud Backup and then tap Back Up Now. When complete, go to Step 2 below.

Step 1 Alternative: Backup your iPhone to your computer: If you don’t intend to restore to a new iPhone or otherwise would like to have your backup on your own computer, use iTunes to perform the backup. Connect your iPhone to your computer via USB and start up iTunes.

Step 1a: Transfer your purchases on the iPhone to iTunes. Find the name of your iPhone in about the middle of the left-hand pane in iTunes. It will appear under the DEVICES heading. Right-click your iPhone and select Transfer Purchases.

Step 1b: Sync your iPhone: right-click your iPhone and select Sync.

Step 1c: Backup your iPhone via iTunes. Yes, the sync backed up your iPhone, but I have found that this backup leaves out some crucial data such as the arrangement of your apps and folders. Right-click your iPhone and select Backup.

Step 2: Reset Home Screen Layout:  On your iPhone, go to SettingsGeneralReset. Tap Reset Home Screen Layout. This sets the home screen to factory default layout. Take your photos now for resale because after you erase all content below, you won’t get a home screen. Instead, the phone will display a Welcome screen that lets the new owner set up the iPhone as they wish.

Step 3: Erase all content on your iPhone: On your iPhone, go to Settings, General, Reset. Tap Erase All Content and Settings.

Step 4: You’re done: When your iPhone finishes erasing, it will reboot and show the iPhone Welcome screen. It is now ready for it’s new owner to configure it as they wish.

Step 5: Paranoid erase all content: iPhone 3GS and later use hardware encryption to protect your data. Apple writes that Erase All Content and Settings “erases user settings and information by removing the encryption key that protects the data.” So if their encryption is unbreakable, there is no need to write over the data. Older models do not provide hardware encryption. On those, Erase All Content and Settings overwrites the data. On the other hand, claims have been made that the encryption has been broken.

Depending on what’s on your phone, you might choose to overwrite it yourself. I suggest using iTunes for that since downloading via the USB port is faster than WiFi. I like to sync videos such as from iTunes U that I don’t care if someone forensically discovers. But first, you’ll need to set up the phone as a new iPhone.

Step 5a: Set up as new iPhone: After your iPhone finishes erasing and rebooting, it will show up in iTunes. If you don’t already see the Welcome to Your New iPhone screen, select iPhone under DEVICES. Choose Set up as new iPhone and click Continue and then Get Started. In the Options section of the Summary tab, uncheck all options except Manually manage music and videos.

Step 5b: Overwrite iPhone: In iTunes, fill the iPhone with data, then remove it. Repeat up to seven times with different combinations of data in proportion to how paranoid you are.

Step 5c: Erase the iPhone: One last time, on your iPhone, go to SettingsGeneralReset. Tap Erase All Content and Settings.

Step 6: Remove the SIM card: There may be personal data on the SIM card. Remove it.

Optional Step 7: Replace the SIM card: If want to be really nice to your phone’s new owner, replace the SIM card. They’re not expensive. Before you buy one, be sure you know which size it takes.

Step 8: Find all the pieces parts, take your final photos, write your copy, post the ad, profit. Be honest!

If at any time you get stuck in the erasing and restoring process and it appears that you’ve bricked your phone, don’t panic. Put the phone into DFU Mode, restore again, and resume where you left off.

Posted in iPhone | Tagged , , , | Leave a comment

DataLocker review: great free app for encrypting your Dropbox files

DataLocker is a free app from AppSense that encrypts files and then lets you share them with Windows, Mac, iPhone and iPad via Dropbox. This is very useful if you have a few (or many) files on your Dropbox for which you’d like a little extra protection.

I downloaded the universal app on iTunes and installed it on both my iPhone and iPad. I requested the Mac version from the AppSense link above. (Awkward and archaic, I know.)

Here’s what I like about these apps:

  • Really easy to use. On the Mac, you just drop files you want encrypted onto the open app and specify a password.
  • The iOS app shows all your Dropbox folders and opens many file types.
  • You can put the encrypted files anywhere and move them around. You’re not restricted to putting them in a fixed or single folder location.
  • Each file is encrypted individually, not altogether into an opaque container. Again, this let’s you move them around even after you’ve encrypted them.

I have two suggestions for AppSense:

  • Let me drag and drop a folder name into the Mac app’s destination folder entry field.
  • Better yet, just encrypt the files in place.
  • You can drop folders into the “drop your files here” box on the Mac app. DataLocker than encrypts all the files and puts them into the destination folder. I would really like to drag a folder and subfolders and have DataLocker encrypt the files in place, preserving the original folder structure.

Did I mention that DataLocker is:

  • Free
  • Easy to use
  • Available for Windows, Mac, iPhone, and iPad
  • Works great with Dropbox
Posted in Encryption | Tagged , , , , | Leave a comment

Drugstore.com: We have bad customer service but we’re really cheerful about it

Blaming Paypal, Drugstore.com cannot ship an item missing from an order without a credit card.

My wife ordered a dozen items from drugstore.com, paying with Paypal. We like that. It minimizes the number of places we’re giving our credit card number.

All but one item arrived in two boxes on the same day. Online, drugstore.com says they completed the order.

My wife filled out a complaint form. She received this in response by email:

Dear _______@yahoo.com,

Thank you for contacting drugstore.com with your question or comment.

We’ve sent this automated response to help you find answers as quickly as possible. Our help center scanned your message and found some information that may be useful for you.

Please reply to this e-mail if the links below don’t help answer your question. If you reply within 7 days with the same subject line (including the incident number 120725-004746) then the help center will send your message directly to a person in our Customer Care department.

If you need help right away, please contact us by phone by calling 1-800-drugstore (1-800-378-4786). We’re here 24 hours a day to take your call.

The following answers might help you immediately. (Answers open in a separate window.)

Title: ShopRunner
Link: http://drugstore.custhelp.com/app/answers/detail/a_id/2112

Title: Shipping Policy
Link: http://drugstore.custhelp.com/app/answers/detail/a_id/189

Title: Shipping Times And Rates – Alaska and Hawaii
Link: http://drugstore.custhelp.com/app/answers/detail/a_id/1132

Title: Shipping Times And Rates – Contiguous United States
Link: http://drugstore.custhelp.com/app/answers/detail/a_id/147

Title: Shipping Times And Rates – U.S. Territories
Link: http://drugstore.custhelp.com/app/answers/detail/a_id/2632

To avoid this auto-reply in the future, you can send your questions or comments to us directly by visiting our help pages:

http://drugstore.custhelp.com/app/ask

Thank you for choosing drugstore.com.

This is a good idea. Maybe we can find the solution to our problem while waiting for a human to read and respond to our complaint. Oops, no. This is the only response. She must file another complaint online to avoid an auto-reply.

So in response to her next complaint, she gets this email. By the way, she gave them the order number and missing item number and description in both emails, but they still can’t help her by simply shipping the missing item:

Dear _________,

Thank you for contacting drugstore.com regarding the issue with your recent drugstore.com order #[redacted].

In response to your email, I apologize for the missing Rusk Thickr Hairspray for Fine or Thin Hair from your drugstore.com order. Please be aware that we are committed to resolving this issue for you in the timeliest manner, and appreciate your patience in this matter.

In order to resolve this matter by sending you a replacement order at no charge, we ask that you please contact us by telephone at 1-800-378-4786. Due to current systems restrictions with PayPal, we kindly ask that you provide us with a credit card in order to complete your replacement order. As your original payment method requires you to sign into your account we are not able to do so, therefore we simply need to have the credit card on file to pass over the payment hurdle of the order placement process. Rest assured that your credit card will not be billed, and you will be able to remove it from your account as soon as your replacement order has completed and shipped.

If you are unable to provide us with credit card information to process your replacement order we would be happy to instead offer you a refund back to the payment method originally charged.

Once again _________, we apologize for the confusion, and look forward to the opportunity to resolve this situation as quickly as possible. Thank you for choosing drugstore.com.

So I called them. A very cheerful, friendly customer service agent repeated that we must give them a credit card but they promise not to charge it. I complained that I don’t want to give them a credit card. That’s why we used Paypal in the first place. Oh, don’t worry, he says, you can log onto your account in a couple days and delete the credit card. They can’t even use the credit card for a one-time non-charge?

I chose the refund and to call out bad customer service here.

Posted in Uncategorized | Tagged

Beginning Storyboards in iOS 5 Part 1 with MonoTouch

Storyboarding is an exciting new feature in iOS 5 that will save you a lot of time building user interfaces for your apps.

This article was adapted from Beginning Storyboards in iOS 5 Part 1, Matthijs Hollemans, by permission of the publisher, Ray Wenderlich. Please visit the Ray Wenderlich iOS Tutorials site for this and many other fine iOS tutorials.

The vast majority of the text of this article is from the original article. Rather than setting the original material in block quotes or double quotes, we’ve chosen instead to highlight the MonoTouch-specific material by setting it in bold.

Until now, my iOS apps have been NIB-free. I wasn’t convinced of the value of using IB until reading the original article on the Ray Wenderlich site. One of my development goals is to increase my productivity. I hope that MonoTouch and storyboards will help achieve that goal.

To show you what a storyboard is, I'll (Note: being in non-bold, that’s Matthijs Hollemans, the author of the original article, writing in the first person) let a picture do the talking. This is the storyboard that we will be building in this tutorial:

The full storyboard we'll be making in this tutorial.

You may not know exactly yet what the app does but you can clearly see which screens it has and how they are related. That is the power of using storyboards.

If you have an app with many different screens then storyboards can help reduce the amount of glue code you have to write to go from one screen to the next. Instead of using a separate nib file for each view controller, your app uses a single storyboard that contains the designs of all of these view controllers and the relationships between them.

Storyboards have a number of advantages over regular nibs:

  • With a storyboard you have a better conceptual overview of all the screens in your app and the connections between them. It's easier to keep track of everything because the entire design is in a single file, rather than spread out over many separate nibs.
  • The storyboard describes the transitions between the various screens. These transitions are called “segues” and you create them by simply ctrl-dragging from one view controller to the next. Thanks to segues you need less code to take care of your UI.
  • Storyboards make working with table views a lot easier with the new prototype cells and static cells features. You can design your table views almost completely in the storyboard editor, something else that cuts down on the amount of code you have to write.

Not everything is perfect, of course, and storyboards do have some limitations. The Storyboard Editor isn't as powerful as Interface Builder yet, there are a few things IB can do that the Storyboard Editor unfortunately can't. You also need a big monitor, especially when you write iPad apps!

If you're the type who hates Interface Builder and who really wants to create his entire UI programmatically, then storyboards are probably not for you. Personally, I prefer to write as little code as possible — especially UI code! — so this tool is a welcome addition to my arsenal.

You can still use nibs with iOS 5 and Xcode 4.2. Using Interface Builder isn't suddenly frowned upon now that we have storyboards. If you want to keep using nibs then go right ahead, but know that you can combine storyboards with nibs. It's not an either-or situation.

In this tutorial we'll take a look at what you can do with storyboards. The app we're going to build is a bit pointless but it does show how to perform the most common tasks that you will be using storyboards for.

Getting Started

Fire up Xcode MonoDevelop and create a new project solution. We'll use the iPhone Storyboard Single View Application template as our starting point and then build up the app from there.

MonoTouch New Solution

Fill in the template options as follows:

  • Product Name: Ratings
  • Location: Folder that contains the solution folder
  • Solution Name: Ratings
  • Company Identifier: the identifier that you use for your apps, in reverse domain notation
  • Class Prefix: leave this empty
  • Device Family: iPhone
  • Use Storyboard: check this
  • Use Automatic Reference Counting: check this
  • Include Unit Tests: this should be unchecked

After Xcode MonoDevelop has created the project, the main Xcode MonoDevelop window looks like this:

MonoDevelop window after creating solution

Our new project consists of two classes, AppDelegate and RatingsViewController, and the star of this tutorial: the MainStoryboard.storyboard file. Notice that there are no .xib files in the project, not even MainWindow.xib.

Let's take a look at that storyboard. Double-click the MainStoryboard.storyboard file in the Project Navigator Solution Pad to open the Xcode Storyboard Editor. MonoDevelop automatically creates this Xcode project that you will use to edit your storyboard.

Storyboard editor

The Storyboard Editor looks and works very much like Interface Builder. You can drag new controls from the Object Library (see bottom-right corner) into your view controller to design its layout. The difference is that the storyboard doesn't contain just one view controller from your app, but all of them.

The official storyboard terminology is “scene”, but a scene is really nothing more than a view controller. Previously you would use a separate nib for each scene / view controller, but now they are all combined into a single storyboard.

On the iPhone only one of these scenes is visible at a time, but on the iPad you can show several at once, for example the master and detail panes in a split-view, or the content of a popover.

To get some feel for how the editor works, drag some controls into the blank view controller:

Dragging controls from Object Library

The sidebar on the left is the Document Outline:

Document outline

In Interface Builder this area lists just the components from your nib but in the Storyboard Editor it shows the contents of all your view controllers. Currently there is only one view controller in our storyboard but in the course of this tutorial we'll be adding several others.

There is a miniature version of this Document Outline below the scene, named the Dock:

The dock in the Storyboard Editor

The Dock shows the top-level objects in the scene. Each scene has at least a First Responder object and a View Controller object, but it can potentially have other top-level objects as well. More about that later. The Dock is convenient for making connections. If you need to connect something to the view controller, you can simply drag to its icon in the Dock.

Note: You probably won't be using the First Responder very much. This is a proxy object that refers to whatever object has first responder status at any given time. It was also present in Interface Builder and you probably never had a need to use it then either. As an example, you could hook up the Touch Up Inside event from a button to First Responder's cut: selector. If at some point a text field has input focus then you can press that button to make the text field, which is now the first responder, cut its text to the pasteboard.

Quit Xcode, run the app from MonoDevelop and it should look exactly like what we designed in the editor:

App with objects

If you've ever made a nib-based app before then you always had a MainWindow.xib file. This nib contained the top-level UIWindow object, a reference to the App Delegate, and one or more view controllers. When you put your app's UI in a storyboard, however, MainWindow.xib is no longer used.

No more MainWindow.xib

So how does the storyboard get loaded by the app if there is no MainWindow.xib file?

Let's take a peek at our application delegate. Open up AppDelegate.h AppDelegate.cs and you’ll see it looks like this:

using System;
using System.Collections.Generic;
using System.Linq;

using MonoTouch.Foundation;
using MonoTouch.UIKit;

namespace Ratings
{
    // The UIApplicationDelegate for the application. This class
    // is responsible for launching the User Interface of the
    // application, as well as listening (and optionally
    // responding) to application events from iOS.
    [Register ("AppDelegate")]
    public partial class AppDelegate : UIApplicationDelegate
    {
        // class-level declarations
        
        public override UIWindow Window {
            get;
            set;
        }

It is a requirement for using storyboards that your application delegate inherits from UIResponder (previously it used to inherit directly from NSObject) and that it has a UIWindow property (unlike before, this is not an IBOutlet). This is handled a little differently in MonoTouch because of how it implements Objective-C protocols.

If you look into further down in AppDelegate.m AppDelegate.cs, you'll see that it does absolutely nothing, all the methods are practically empty. Even application:didFinishLaunchingWithOptions: FinishedLaunching simply returns YES true. Previously, this would either add the main view controller's view to the window or set the window's rootViewController property, but none of that happens here.

The secret is in the Info.plist file. Double-click on Ratings- Info.plist (it's in the Supporting Files group project) and you'll see this:

Info-plist

In nib-based projects there was a key in Info.plist named NSMainNibFile, or “Main nib file base name”, that instructed UIApplication to load MainWindow.xib and hook it into the app. Our Info.plist no longer has that setting.

Instead, storyboard apps use the key UIMainStoryboardFile, or “Main storyboard file base name”, to specify the name of the storyboard that must be loaded when the app starts. When this setting is present, UIApplication will load the MainStoryboard.storyboard file and automatically instantiates the first view controller from that storyboard and puts its view into a new UIWindow object. No programming necessary.

You can also see this in the Target Summary screen: The MonoDevelop iOS Application Target panel looks very much like the Xcode Target Summary screen:

Setting Main Storyboard in Target summary

There is a new iPhone/iPod Deployment Info section that lets you choose between starting from a storyboard or from a nib file. I don’t see any evidence that MonoDevelop let’s you choose betwen starting from a storyboard or from a nib file at this point. You may recall you had that choice when you created the project. (See Getting Started above.)

While we’ve got the Info.plist open, let’s fill in the iOS Application Target information:

iOS Application Target information

  • Application name: Ratings
  • Identifier: identifier you use for your apps, in reverse domain notation
  • Version: version number useful for your customers
  • Devices: iPhone/iPod
  • Deployment Target: 5.0

For the sake of completeness, also open main.m main.cs to see what’s in there:

using System;
using System.Collections.Generic;
using System.Linq;

using MonoTouch.Foundation;
using MonoTouch.UIKit;

namespace Ratings
{
    public class Application
    {
        // This is the main entry point of the application.
        static void Main (string[] args)
        {
            // if you want to use a different Application
            // Delegate class from "AppDelegate" you can
            // specify it here.
            UIApplication.Main (args, null, "AppDelegate");
        }
    }
}

Previously, the last parameter for UIApplicationMain() was nil but now it is NSStringFromClass([AppDelegate class]) “AppDelegate”.

A big difference with having a MainWindow.xib is that the app delegate is not part of the storyboard. Because the app delegate is no longer being loaded from a nib (nor from the storyboard), we have to tell UIApplicationMain specifically what the name of our app delegate class is, otherwise it won't be able to find it.

(If you’d like to specify the class name more like the Xcode example, use typeof(AppDelegate).Name in place of NSStringFromClass([AppDelegate class]). Personally, I wouldn’t bother as I don’t see any value in “fixing” code MonoDevelop already generated correctly.)

Just Add It To My Tab

Our Ratings app has a tabbed interface with two screens. With a storyboard it is really easy to create tabs.

Switch back to Double-click MainStoryboard.storyboard to open the storyboard in Xcode, and drag a Tab Bar Controller from the Object Library into the canvas. You may want to maximize your Xcode window first, because the Tab Bar Controller comes with two view controllers attached and you'll need some room to maneuver.

Adding a new tab bar controller into the Storyboard

The new Tab Bar Controller comes pre-configured with two other view controllers, one for each tab. UITabBarController is a so-called container view controller because it contains one or more other view controllers. Two other common containers are the Navigation Controller and the Split View Controller (we'll see both of them later). Another cool addition to iOS 5 is a new API for writing your own container controllers – and later on in this book, we have a tutorial on that! (The author is referring to the book, iOS 5 By Tutorials.)

The container relationship is represented in the Storyboard Editor by the arrows between the Tab Bar controller and the view controllers that it contains.

Relationship arrow in the Storyboard editor

Note: If you want to move the Tab Bar controller and its attached view controllers as a group, you can Cmd-click to select multiple scenes and then move them around together. (Selected scenes have a thick blue outline.)

Drag a label into the first view controller and give it the text “First Tab”. Also drag a label into the second view controller and name it “Second Tab”. This allows us to actually see something happen when you switch between the tabs.

Note: You can't drag stuff into the scenes when the editor is zoomed out. You'll need to return to the normal zoom level first.

Select the Tab Bar Controller and go to the Attributes Inspector. Check the box that says Is Initial View Controller.

Is Initial View Controller attribute

In the canvas the arrow that at first pointed to the regular view controller now points at the Tab Bar Controller:

Arrow indicating initial view controller in Storyboard editor

This means that when you run the app, UIApplication will make the Tab Bar Controller the main screen of our app.

The storyboard always has a single view controller that is designated the initial view controller, that serves as the entry point into the storyboard.

Quit Xcode, switch back to MonoDevelop, run the app and try it out. The app now has a tab bar and you can switch between the two view controllers with the tabs:

App with tab bar

Xcode MonoDevelop actually comes with a template for building a tabbed app (unsurprisingly called the Tabbed Application template) that we could have used, but it's good to know how this works so you can also create one by hand if you have to.

You can remove the view controller that was originally added by the template as we'll no longer be using it. The storyboard now contains just the tab bar and the two scenes for its tabs.

By the way, if you connect more than five scenes to the Tab Bar Controller, it automatically gets a More… tab when you run the app. Pretty neat!

Adding a Table View Controller

The two scenes that are currently attached to the Tab Bar Controller are both regular UIViewControllers. I want to replace the scene from the first tab with a UITableViewController instead.

Click on that first view controller to select it and then delete it. From the Object Library drag a new Table View Controller into the canvas in the place where that scene used to be:

Adding a new table view controller to the Storyboard

With the Table View Controller selected, choose Editor\Embed In\Navigation Controller from Xcode's menubar. This adds yet another view controller to the canvas:

Embedding in a navigation controller

You could also have dragged in a Navigation Controller from the Object Library, but this Embed In command is just as easy.

Because the Navigation Controller is also a container view controller (just like the Tab Bar Controller), it has a relationship arrow pointing at the Table View Controller. You can also see these relationships in the Document Outline:

View controller relationships in outline of Storyboard editor

Notice that embedding the Table View Controller gave it a navigation bar. The Storyboard Editor automatically put it there because this scene will now be displayed inside the Navigation Controller's frame. It's not a real UINavigationBar object but a simulated one.

If you look at the Attributes Inspector for the Table View Controller, you'll see the Simulated metrics section at the top:

Simulated metrics in Storyboard editor

“Inferred” is the default setting for storyboards and it means the scene will show a navigation bar when it's inside of a navigation controller, a tab bar when it's inside of a tab bar controller, and so on. You could override these settings if you wanted to, but keep in mind they are here only to help you design your screens. The Simulated Metrics aren't used during runtime, they're just a visual design aid that shows what your screen will end up looking like.

Let's connect these new scenes to our Tab Bar Controller. Ctrl-drag from the Tab Bar Controller to the Navigation Controller:

Connecting scenes in the storyboard

When you let go, a small popup menu appears:

Create relationship segue

Choose the Relationship – viewControllers option. This creates a new relationship arrow between the two scenes:

Relationship arrow in the Storyboard editor

The Tab Bar Controller has two such relationships, one for each tab. The Navigation Controller itself has a relationship connection to the Table View Controller. There is also another type of arrow, the segue, that we'll talk about later.

When we made this new connection, a new tab was added to the Tab Bar Controller, simply named “Item”. I want this new scene to be the first tab, so drag the tabs around to change their order:

Rearranging tabs in the Storyboard editor

Quit Xcode, run the app from MonoDevelop and try it out. The first tab now contains a table view inside a navigation controller.

App with table view

Before we put some actual functionality into this app, let's clean up the storyboard a little. I want to name the first tab “Players” and the second “Gestures”. You do not change this on the Tab Bar Controller itself, but in the view controllers that are connected to these tabs.

As soon as you connect a view controller to the Tab Bar Controller, it is given a Tab Bar Item object. You use the Tab Bar Item to configure the tab's title and image.

Select the Tab Bar Item inside the Navigation Controller and in the Attributes Inspector set its Title to “Players”:

Setting the title of a Tab Bar Item

Rename the Tab Bar Item for the view controller from the second tab to “Gestures”. Quit Xcode and return to MonoDevelop.

We should also put some pictures on these tabs. The resources for this tutorial contains a subfolder named Images. Add that folder to the project in MonoDevelop by right-clicking the project and selecting Add→Add Existing Folder:

Add Existing Folder

You might also want to add the images folder to the Xcode project so that you can see the images on the storyboard. Sadly, I don’t know how to do that without putting the images in the top level folder of the MonoDevelop project. I tried to add into Xcode a reference to the Images folder in the MonoDevelop project so you can select the images from the Xcode Attributes Inspector:

In the Attributes Inspector for the Players Tab Bar Item, choose the Players.png image. You probably guessed it, but give the Gestures item the image Gestures.png.

But it does not work in MonoDevelop. In Xcode, change Players.png to Images/Players.png in the Attributes Inspector for the Players Tab Bar Item. This should now working in MonoDevelop. Again, to fix this, copy the images into the top level of the MonoDevelop project, not within an Images folder. I prefer the little hassle in Xcode to having all these files visible at the top level in MonoDevelop. You may have a different preference.

If anyone knows how to fix this, please leave a comment below and I’ll update the article. Thanks.

Similarly, a view controller inside a Navigation Controller has a Navigation Item that is used to configure the navigation bar. (By now, you know enough to double-click the storyboard file in MonoDevelop, edit the storyboard, and quit Xcode to return to MonoDevelop to test, so I’ll stop the instructions about that.) Select the Navigation Item for the Table View Controller and change its title in the Attributes Inspector to “Players”.

Alternatively, you can simply double-click the navigation bar and change the title there. (Note: You should double-click the simulated navigation bar in the Table View Controller, not the actual Navigation Bar object in the Navigation Controller.)

Changing the title in a Navigation Item

Run the app and marvel at our pretty tab bar, all without writing a single line of code!

App with the final tabs

Prototype cells

You may have noticed that ever since we added the Table View Controller, Xcode has been complaining:

Xcode warning: Prototype cells must have reuse identifiers

The warning message is, “Unsupported Configuration: Prototype table cells must have reuse identifiers”. When you add a Table View Controller to a storyboard, it wants to use prototype cells by default but we haven't properly configured this yet, hence the warning. I don’t get this warning in MonoDevelop, but let’s learn about prototype cells because:

Prototype cells are one of the cool advantages that storyboards offer over regular nibs. Previously, if you wanted to use a table view cell with a custom design you either had to add your subviews to the cell programmatically, or create a new nib specifically for that cell and then load it from the nib with some magic. That's still possible, but prototype cells make things a lot easier for you. Now you can design your cells directly in the storyboard editor.

The Table View Controller comes with a blank prototype cell. Click on that cell to select it and in the Attributes Inspector set Style to Subtitle. This immediately changes the appearance of the cell to include two labels. If you've used table views before and created your own cells by hand, you may recognize this as the UITableViewCellStyleSubtitle style. With prototype cells you can either pick one of the built-in cell styles as we just did, or create your own custom design (which we'll do shortly).

Creating a Prototype cell

Set the Accessory attribute to Disclosure Indicator and give the cell the Reuse Identifier “PlayerCell“. That will make Xcode shut up about the warning. All prototype cells are still regular UITableViewCell objects and therefore should have a reuse identifier. Xcode is just making sure we don't forget (at least for those of us who pay attention to its warnings).

Run the app, and… nothing has changed. That's not so strange, we still have to make a data source for the table so it will know what rows to display.

Add a new file to the project. Choose the UIViewController subclass template. Name the class PlayersViewController and make it a subclass of UITableViewController. The With XIB for user interface option should be unchecked because we already have the design of this view controller in the storyboard. No nibs today! We’re going to let MonoDevelop do it’s magic here and generate the PlayersViewController class for us.

Go back to the Storyboard Editor and select the Table View Controller. In the Identity Inspector, set its Class to PlayersViewController. That is the essential step for hooking up a scene from the storyboard with your own view controller subclass. Don't forget this or your class won't be used!

Setting the class name in the identity inspector

Quit Xcode and return to MonoDevelop. Wait a moment while MonoDevelop automatically generates the PlayersViewController class and adds it to your project.

// This file has been autogenerated from parsing an Objective-C header file added in Xcode.

using System;

using MonoTouch.Foundation;
using MonoTouch.UIKit;

namespace Ratings
{
    public partial class PlayersViewController : UITableViewController
    {
        public PlayersViewController (IntPtr handle) : base (handle)
        {
        }
    }
}

From now on when you run the app that table view controller from the storyboard is actually an instance of our PlayersViewController class.

Add a new MonoTouch Empty Class to the project. Name it TableSource, subclass of UITableViewSource. The UITableViewSource class is a convenience class that combines the UITableViewDataSource and UITableViewDelegate protocols into a single class. Add a mutable array property private instance variable to PlayersViewController.h TableSource.cs:

using System;
using System.Collections.Generic;
using System.IO;
using MonoTouch.Foundation;
using MonoTouch.UIKit;

namespace Ratings
{
    public class TableSource : UITableViewSource
    {
        private IList&lt;Player&gt; players;

        public TableSource (IList&lt;Player&gt; players)
        {
            this.players = players;
        }
    }
}

This array list will contain the main data model for our app. It contains Player objects. Let's make that Player class right now. Add a new file to the project using the Objective-C MonoDevelop Empty Class template. Name it Player, subclass of NSObject.

Change Player.h Player.cs to the following:

using System;

namespace Ratings
{
    public class Player
    {
        public String Name { get; set; }
        public String Game { get; set; }
        public int    Rating { get; set; }

        public Player ()
        {
        }
    }
}

There's nothing special going on here. Player is simply a container object for these three properties: the name of the player, the game he's playing, and a rating (1 to 5 stars).

We'll make the array list and some test Player objects in our App Delegate and then assign it to the PlayersViewController TableSource's players property.

In AppDelegate.m AppDelegate.cs, add an #import for the Player and PlayersViewController classes at the top of the file, and add a new instance variable named players:

private List&lt;Player&gt; players;

Then change the didFinishLaunchingWithOptions FinishedLaunching method to:

public override bool FinishedLaunching (
    UIApplication application,
    NSDictionary launchOptions)
{
    players = new List&lt;Player&gt;()
    {
        new Player()
        {
            Name = "Bill Evans",
            Game = "Tic-Tac-Toe",
            Rating = 4,
        },
        new Player()
        {
            Name = @"Oscar Peterson",
            Game = @"Spin the Bottle",
            Rating = 5,
        },
        new Player()
        {
            Name = @"Dave Brubeck",
            Game = @"Texas Hold’em Poker",
            Rating = 2,
        },
    };

    // Dig through storyboard to find PlayersViewController
    UITabBarController tbc =
        this.Window.RootViewController as UITabBarController;
    UINavigationController nc =
        tbc.ViewControllers[0] as UINavigationController;
    PlayersViewController pvc =
        nc.ViewControllers[0] as PlayersViewController;

    pvc.TableView.Source = new TableSource(players);

    return true;
}

This simply creates some Player objects and adds them to the players array list. But then it does the following:

UITabBarController tbc =
    this.Window.RootViewController as UITabBarController;
UINavigationController nc =
    tbc.ViewControllers[0] as UINavigationController;
PlayersViewController pvc =
    nc.ViewControllers[0] as PlayersViewController;

Yikes, what is that?! We want to assign the players array to the players property of create an instance of TableSource as the data source for PlayersViewController so it can use this array for its data source. But the app delegate doesn't know anything about PlayersViewController yet, so it will have to dig through the storyboard to find it.

This is one of the limitations of storyboards that I find annoying. With Interface Builder you always had a reference to the App Delegate in your MainWindow.xib and you could make connections from your top-level view controllers to outlets on the App Delegate. That is currently not possible with storyboards. You cannot make references to the app delegate from your top-level view controllers. That's unfortunate, but we can always get those references programmatically.

UITabBarController tbc =
    this.Window.RootViewController as UITabBarController;

We know that the storyboard's initial view controller is a Tab Bar Controller, so we can look up the window's rootViewController and cast it.

The PlayersViewController sits inside a navigation controller in the first tab, so we look up that UINavigationController object:

UINavigationController nc =
    tbc.ViewControllers[0] as UINavigationController;

and then ask it for its root view controller, which is the PlayersViewController that we are looking for:

PlayersViewController pvc =
    nc.ViewControllers[0] as PlayersViewController;

Unfortunately, UINavigationController has no rootViewController property so we'll have to delve into its viewControllers array. (It does have a topViewController property but that points to the top-most controller on the stack and we're looking for the bottom-most one. At this point the app has just launched so technically we could have used topViewController, but that is not always the case.)

Now that we have an array full initialized our list of Player objects, we can continue building the data source for PlayersViewController.

Open up PlayersViewController.m, and change the table view data source methods to the following: Open up TableSource.cs and add the following table view data source methods:

public override int NumberOfSections (UITableView tableView)
{
    return 1;
}

public override int RowsInSection (UITableView tableview, int section)
{
    return players.Count;
}

The real work happens in cellForRowAtIndexPath. The version from the Xcode template looks like this:

 
- (UITableViewCell *)tableView:(UITableView *)tableView 
  cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [tableView 
      dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] 
          initWithStyle:UITableViewCellStyleDefault 
          reuseIdentifier:CellIdentifier];
    }
 
    // Configure the cell...
    return cell;
}

That is no doubt how you've been writing your own table view code all this time. Well, no longer! Replace that method with:

public override UITableViewCell GetCell (
    UITableView tableView,
    MonoTouch.Foundation.NSIndexPath indexPath)
{
    UITableViewCell cell =
        tableView.DequeueReusableCell ("PlayerCell") as UITableViewCell;

    Player player             = players[indexPath.Row];
    cell.TextLabel.Text       = player.Name;
    cell.DetailTextLabel.Text = player.Game;
    return cell;
}

That looks a lot simpler! The only thing you need to do to get a new cell is:

UITableViewCell cell =
    tableView.DequeueReusableCell ("PlayerCell") as UITableViewCell;

If there is no existing cell that can be recycled, this will automatically make a new copy of the prototype cell and return it to you. All you need to do is supply the reuse identifier that you set on the prototype cell in the storyboard editor, in our case “PlayerCell“. Don't forget to set that identifier, or this little scheme won't work!

Because this class doesn't know anything about the Player object yet, it needs an #import at the top of the file:

And we should not forget to synthesize the property that we added earlier:

Now you can run the app, and lo and behold, the table view has players in it:

Table view with data

Note: In this app we're using only one prototype cell but if your table needs to display different kinds of cells then you can simply add additional prototype cells to the storyboard. You can either duplicate the existing cell to make a new one, or increment the value of the Table View's Prototype Cells attribute. Be sure to give each cell its own re-use identifier, though.

It just takes one line of code to use these newfangled prototype cells. I think that's just great!

Designing Our Own Prototype Cells

Using a standard cell style is OK for many apps, but I want to add an image on the right-hand side of the cell that shows the player's rating (in stars). Having an image view in that spot is not supported by the standard cell styles, so we'll have to make a custom design.

Switch back to MainStoryboard.storyboard, select the prototype cell in the table view, and set its Style attribute to Custom. The default labels now disappear.

First make the cell a little taller. Either drag its handle at the bottom or change the Row Height value in the Size Inspector. I used the latter method to make the cell 55 points high. And I used the former for MonoTouch because it seemed easier.

Drag two Label objects from the Objects Library into the cell and place them roughly where the labels were previously. Just play with the font and colors and pick something you like. Do set the Highlighted color of both labels to white. That will look better when the user taps the cell and the cell background turns blue.

Drag an Image View into the cell and place it on the right, next to the disclosure indicator. Make it 81 points wide, the height isn't very important. Set its Mode to Center (under View in the Attributes Inspector) so that whatever image we put into this view is not stretched.

I made the labels 210 points wide so they don't overlap with the image view. The final design for the prototype cell looks something like this:

Prototype cells with a custom design

Because this is a custom designed cell, we can no longer use UITableViewCell's textLabel and detailTextLabel properties to put text into the labels. These properties refer to labels that aren't on our cell anymore. Instead, we will use tags to find the labels.

Give the Name label tag 100, the Game label tag 101, and the Image View tag 102. You can do this in the Attributes Inspector.

Then open PlayersViewController.m TableSource.cs and change cellForRowAtIndexPath from PlayersViewController GetCell from TableSource to:

public override UITableViewCell GetCell (
    UITableView tableView,
    MonoTouch.Foundation.NSIndexPath indexPath)
{
    UITableViewCell cell =
        tableView.DequeueReusableCell ("PlayerCell") as UITableViewCell;

    Player player     = players[indexPath.Row];

    UILabel nameLabel = cell.ViewWithTag(100) as UILabel;
    nameLabel.Text    = player.Name;

    UILabel gameLabel = cell.ViewWithTag(101) as UILabel;
    gameLabel.Text    = player.Game;

    UIImageView ratingImageView = cell.ViewWithTag(102) as UIImageView;
    ratingImageView.Image = this.ImageForRating(player.Rating);

    return cell;
}

This uses a new method, ImageForRating. Add that method above (or below — this is C#!) cellForRowAtIndexPath GetCell:

private UIImage ImageForRating(int rating)
{
    switch (rating)
    {
        case 1: return UIImage.FromFile("Images/1StarSmall.png");
        case 2: return UIImage.FromFile("Images/2StarsSmall.png");
        case 3: return UIImage.FromFile("Images/3StarsSmall.png");
        case 4: return UIImage.FromFile("Images/4StarsSmall.png");
        case 5: return UIImage.FromFile("Images/5StarsSmall.png");
    }
    return null;
}

That should do it. Now run the app again.

Wrong cell height for custom cells made in Storyboard editor

Hmm, that doesn't look quite right. We did change the height of the prototype cell but the table view doesn't automatically take that into consideration. There are two ways to fix it: we can change the table view's Row Height attribute or implement the heightForRowAtIndexPath method. The former is much easier, so let's do that.

Note: You would use heightForRowAtIndexPath if you did not know the height of your cells in advance, or if different rows can have different heights.

Back in MainStoryboard.storyboard, in the Size Inspector of the Table View, set Row Height to 55:

Setting the table view row height

By the way, if you changed the height of the cell by dragging its handle rather than typing in the value, then the table view's Row Height property was automatically changed too. So it may have worked correctly for you the first time around.

If you run the app now, it looks a lot better!

Using a Subclass for the Prototype Cell

Our table view already works pretty well but I'm not a big fan of using tags to access the labels and other subviews of the prototype cell. It would be much more handy if we could connect these labels to outlets and then use the corresponding properties. As it turns out, we can.

Add a new file to the project, with the Objective-C class template. Name it PlayerCell and make it a subclass of UITableViewCell.

Back in MainStoryboard.storyboard, select the prototype cell and change its Class to “PlayerCell“ on the Identity Inspector. Xcode automatically adds the class PlayerCell as a subclass of UITableViewCell. Now whenever you ask the table view for a new cell with dequeueReusableCellWithIdentifier, it returns a PlayerCell instance instead of a regular UITableViewCell.

It would be great if you could immediately add the outlets, but I couldn’t get Xcode to recognize the existence of the new PlayerCell class if I added them now. So quit Xcode, go back to MonoDevelop, and wait for MonoDevelop to detect the changes in Xcode and automatically add the new class, PlayerCell. Then restart Xcode and make the following changes to PlayerCell in Xcode:

Change PlayerCell.h to:

@interface PlayerCell : UITableViewCell
 
@property (nonatomic, strong) IBOutlet UILabel *nameLabel;
@property (nonatomic, strong) IBOutlet UILabel *gameLabel;
@property (nonatomic, strong) IBOutlet UIImageView 
  *ratingImageView;
 
@end

Replace the contents of PlayerCell.m with:

#import "PlayerCell.h"
 
@implementation PlayerCell
 
@synthesize nameLabel;
@synthesize gameLabel;
@synthesize ratingImageView;
 
@end

The class itself doesn't do much, it just adds properties for nameLabel, gameLabel and ratingImageView.

Note that I gave this class the same name as the reuse identifier — they're both called PlayerCell — but that's only because I like to keep things consistent. The class name and reuse identifier have nothing to do with each other, so you could name them differently if you wanted to.

Now you can connect the labels and the image view to these outlets. Either select the label and drag from its Connections Inspector to the table view cell, or do it the other way around, ctrl-drag from the table view cell back to the label:

Connecting the player cell

Important: You should hook up the controls to the table view cell, not to the view controller! You see, whenever your data source asks the table view for a new cell with dequeueReusableCellWithIdentifier, the table view doesn't give you the actual prototype cell but a *copy* (or one of the previous cells is recycled if possible). This means there will be more than one instance of PlayerCell at any given time. If you were to connect a label from the cell to an outlet on the view controller, then several copies of the label will try to use the same outlet. That's just asking for trouble. (On the other hand, connecting the prototype cell to actions on the view controller is perfectly fine. You would do that if you had custom buttons or other UIControls on your cell.)

Now that we've hooked up the properties, we can quit Xcode, return to MonoDeveop, and simplify our data source code one more time.

public override UITableViewCell GetCell (
    UITableView tableView,
    MonoTouch.Foundation.NSIndexPath indexPath)
{
    PlayerCell cell =
        tableView.DequeueReusableCell ("PlayerCell") as PlayerCell;

    Player player          = players[indexPath.Row];
    cell.nameLabel.Text    = player.Name;
    cell.gameLabel.Text    = player.Game;
    cell.ratingImageView.Image = this.ImageForRating(player.Rating);

    return cell;
}

That's more like it. We now cast the object that we receive from dequeueReusableCellWithIdentifier to a PlayerCell, and then we can simply use the properties that are wired up to the labels and the image view. I really like how using prototype cells makes table views a whole lot less messy!

You'll need to import the PlayerCell class to make this work:

Run the app and try it out. Oops, it doesn’t build. Edit PlayerCell.designer.cs. Make nameLabel, gameLabel, and ratingImageView public. Now build and run. When you run the app it should still look the same as before, but behind the scenes we're now using our own table view cell subclass!

Here are some free design tips. There are a couple of things you need to take care of when you design your own table view cells. First off, you should set the highlighted color of the labels so that they look good then the user taps the row:

Selecting the proper highlight color

Second, you should make sure that the content you add is flexible so that when the table view cell resizes, the content sizes along with it. Cells will resize when you add the ability to delete or move rows, for example.

Add the following method to PlayersViewController.m TableSource.cs:

public override void CommitEditingStyle (
    UITableView tableView,
    UITableViewCellEditingStyle editingStyle,
    NSIndexPath indexPath)
{
    if (editingStyle == UITableViewCellEditingStyle.Delete)
    {
        this.players.RemoveAt(indexPath.Row);
        tableView.DeleteRows(new NSIndexPath[] { indexPath }, UITableViewRowAnimation.Fade);
    }
}

When this method is present, swipe-to-delete is enabled on the table. Run the app and swipe a row to see what happens.

The delete button overlapping a table view cell's content

The Delete button slides into the cell but partially overlaps the stars image. What actually happens is that the cell resizes to make room for the Delete button, but the image view doesn't follow along.

To fix this, open MainStoryBoard.storyboard, select the image view in the table view cell, and in the Size Inspector change the Autosizing so it sticks to its superview's right edge:

Autosizing attributes for the image view

Autosizing for the labels should be set up as follows, so they'll shrink when the cell shrinks:

Autosizing attributes for the labels

With those changes, the Delete button appears to push aside the stars:

Delete button appearing with proper autosizing

You could also make the stars disappear altogether to make room for the Delete button, but that's left as an exercise for the reader. The important point is that you should keep these details in mind when you design your own table view cells!

Where To Go From Here?

Check out part two of this tutorial, where we’ll cover segues, static table view cells, the add player screen, a game picker screen, and the downloadable example project for this tutorial! Presently, part two has not been adapted for MonoTouch. That, too, is left as an exercise for the reader.

This “Beginning Storyboards in iOS 5″ series is one of the chapters in our new iOS 5 By Tutorials book. If you like what you see here, check out the book – there’s an entire second chapter on intermediate storyboarding, above and beyond what we’re posting for free here! :]

If you felt lost at any point during this tutorial, you also might want to brush up on the basics with my newly updated iOS Apprentice series. In that series, I cover the foundational knowledge you need as an iOS developer from the ground up – perfect for complete beginners, or those looking to fill in some gaps. To brush up on MonoTouch and MonoDevelop, see the many tutorials, samples, and recipes at Xamarin.

If you have any questions or comments on this tutorial or on storyboarding in iOS 5 in general, please join the forum discussion below! For MonoTouch and MonoDevelop, try Stack Overflow or the Xamarin iOS forums.

This is a post by iOS Tutorial Team member Matthijs Hollemans, an experienced iOS developer and designer.

This iOS Tutorial post was adapted for MonoTouch by Terry Westley, a not so experienced iOS developer.

Posted in iPhone, iPhone Dev | Tagged , , , , , , | 3 Comments

STOP SOPA

We sometimes complain about government limitations on our freedom. We sometimes complain about misuse at the hands of big corporations.

But there’s just nothing like both of them conspiring together. Read all you can about PROTECT-IP and SOPA and then call your representatives.

Tumblr is a good place to start.

Posted in Free Speech | Leave a comment