How to Make a Simple Mac App on OS X 10.7 Tutorial: Part 3/3

Ernesto García Ernesto García
It's time to show these Scary Bugs in a Mac Application! :]

It’s time to show these Scary Bugs in a Mac Application! :]

This is a post by iOS Tutorial Team Member Ernesto García, a Mac and iOS developer founder of CocoaWithChurros.

Welcome back to the third and final part of the How to Create a Simple Mac App tutorial series!

In the first part of the series, you created a Mac app that showed a list of Scary Bugs.

In the second part of the series, you covered how to show the details for the bugs, and how to add, delete and modify bugs.

In this third part, we’ll cover how to polish and make your app provide a better user experience.

By the time you’re done this tutorial, you will have created a complete Mac OSX app – and hopefully you’ll be inspired to create some Mac apps of your own! :]

What’s wrong with this app?

Everything works great so far. You can see a bunch of scary bugs, add or delete bugs, and you can even change any information on a bug.

It’s functionally complete. But as is, you are not providing a very good user experience.

For example, if you resize the window and make it very large, look what happens to the poor controls!

They are not resized, and they are totally misaligned, making the app look very ugly and unprofessional.

It’s even worse if you make your window small:

Ack! In this case you cannot even see all the information you need. It’s clear that you need to set a minimum window size so that your app is usable.

So, let’s first fix the resizing issues by setting a minimum window size.

Select MasterViewController.xib. Resize the view and arrange the controls in a way that you feel they look good and that you can see the information you need with the minimum size, perhaps like this:

We’ve also arranged to control so that all the buttons are aligned vertically, and all the controls in the detail view are aligned horizontally and have the same width.

Now let’s add a little detail to make it look better, and to make the separation between the list and the detail section clearer. We’re going to add a separator line.

Select Vertical Line in the Objects Library, and drag it onto our view. Place it between the list and the detail controls, just in the middle of the empty space.

After these changes, look in the size inspector and record the size of your view. In my case, it was 542×386 pixels, but yours may vary. This is OK, just write down what it is for you.

That’s going to be the minimum size for your application window. Now, select MainMenu.xib, and then select the Window of the app. In the size inspector check the Minimum Size option and change the width and the height fields to the values you wrote down earlier.

Compile and run the application, and try resizing the window:

You’ll see you can still resize the window, but it won’t get any smaller than the minimum size you defined. With this change, our information is always properly visible, w00t!

Now it’s time to handle the resizing, which requires a bit of thought. Your window has two different parts: the table view, and the details section. They should behave differently when you resize the window.

You want our list view to grow vertically when the window is resized, but you want its width to stay constant, irrespective of the window size. However, you want the detail side to expand as the window grows larger.

Let’s start by setting up the table view to resize properly. Select MasterViewController.xib, and select your table view. Switch over to the Size Inspector tab and change the autosizing settings to the following:

This will make it so that when your view is resized, the table also resizes vertically, but its width does not change.

Next select the separator line that you added, and set the autosizing settings exactly the same. You want that control to behave the same as the list view.

Now you need to configure the autosizing for the “Add” and “Delete” buttons. you don’t want these buttons to change their size, but you need to keep their distance to the bottom of the window constant, so that they are always located below the table view.

Set the autoresizing setting for both buttons like the following:

Compile and run the application, and try resizing the window. Great success!

Now you can resize the window, and the table view grows vertically to fit it. The buttons also change their position to stay always below the table view.

But the details section is not looking good yet. Let’s change that. In the details section, you need the controls to resize horizontally when the window’s width increases.

Select the text field, and change the autoresizing settings to this:

We’ve just configured the text field to resize horizontally when the window’s width increases, but to keep constant its height and vertical position.

Now repeat this exact configuration with the two labels and the Rating Control.

The button and the image view need a different configuration.

First, let’s configure the button to change the picture. You want that button to resize horizontally. But you also need it to change its vertical position when the window is resized. To achieve that, change the button settings to this:

This configuration allows the button to change its width, and to change its vertical position to stay at the same distance to the bottom edge of the window.

And finally, the image view. This one is different too. you need the image to grow up vertically and horizontally to fill the space between the button and the rating view.

Change the image view settings to the following:

Compile and run the application, and try resizing again.

If you resize the app, you can see how all the controls grow up to fit all the available space, and change their position accordingly. Now it’s looking much better!

If you feel that the app does not look good when you make it very large, you can also set the maximum size of the window the same way you changed the minimum.

Just go to the Size Inspector panel of the main window, and check the maximum size option. We’ll leave that as an exercise for you!

Note: As you can see, it is very important to make sure your view controllers can adapt to different window sizes on the Mac. Using autosizing attributes like we just did here is definitely the easiest way to get things working, but if you want more power and flexibility you might want to look into the new Auto Layout capabilities. Auto Layout is beyond the scope of this tutorial, but we cover it in great detail in our upcoming book iOS 6 by Tutorials – so if you want to learn more about it stay tuned for that! :]

Attention to details

Now our app is working fine, and it’s able to adapt to the window size changes. The user interface looks better and more professional than the previous one.

So, what else is there to do? Not much, just a few small details that can make the user experience a little better.

Let’s see a couple of examples. Compile and run the application, and without selecting anything, click the “Delete” or the “Change Picture” buttons. You can click them, but nothing happens, right?

Since you’re the developer of the app, you know that those buttons don’t do anything when there is no selection. But the user might not know that, so the following situation could occur:

User getting upset that button doesn't appear to do anything comic

This is the kind of situation you should avoid so your users have a better experience. These small details add up, and fixing them will make your app look polished.

So, let’s try to fix all these small problems, which are related to the table selection. Whenever the selection changes:

  • If a row is selected, you need to enable the “Delete” button, the “Change picture” button, the text field and the rating view.
  • If the table does not have any selection, you just need to disable them, so that the user cannot interact with those controls.

In Interface Builder, you’re going to set the buttons and text editor disabled by default. Select MasterViewController.xib. Select the “Delete” button, and open the Attributes Inspector. Scroll down until you find the property “Enabled” and uncheck it.

Repeat this for the Change Picture button, and the text field.

This way, those controls are disabled by default when the application starts. you’ll need to re-enable them when the user selects a row in the table view.

In order to enable those controls, you need to add a property in the view controller. Let’s do it first with the Delete button.

Bring up the Assistant Editor (second button under the “Editor” section in the top toolbar), and make sure it’s set to Automatic\MasterViewController.m.

Select the “Delete” button, and control-drag from the button into MasterViewController.m, at the beginning of the file, right after the line @property (weak) IBOutlet EDStarRating *bugRating;

A popup will appear allowing you to hook the NSButton up to a property in your class. Make sure the connection property in that popup is “Outlet”, name it deleteButton, and click Connect.

Repeat the same operation for the Change Picture button, and name it changePictureButton.

Now let’s add the code that handles the status of those controls based on the table selection.
Select MasterViewController.m, and add the following code in the tableViewSelectionDidChange: method, just below the line [self setDetailInfo:selectedDoc];

// Enable/Disable buttons based on selection
BOOL buttonsEnabled = (selectedDoc!=nil);
[self.deleteButton setEnabled:buttonsEnabled];
[self.changePictureButton setEnabled:buttonsEnabled];
[self.bugRating setEditable:buttonsEnabled];
[self.bugTitleView setEnabled:buttonsEnabled];

In this code, you determine if the controls need to be enabled or not based on the user selection. If the selectedDoc is nil, it means that no rows are selected so the controls should be disabled.

Likewise, if the user selects a bug, they should be enabled again.

There is one more step. The rating view is enabled by default, and you also want it to be disabled when the application starts. Since it’s a custom view, it cannot be enabled/disabled in Interface Builder.

You need to disable it programmatically when the application starts. you can do that in loadView of the MasterViewController.

Select MasterViewController.m and in loadView, change the line:

self.bugRating.editable=YES;

to this:

self.bugRating.editable=NO;

With this change, you set the control as not editable by default. The control is shown, but the rating cannot be changed unless the user selects a bug.

Compile and run the application.

When the application starts, you can see that the controls are disabled. When you select a bug, they’re all enabled back.

And when the row is deselected or deleted, they become disabled, because there are no bugs selected.

Note: You could have also solved this problem by making the detail views hidden until you select a bug. It’s up to your personal preference and what works best for your app.

Where To Go From Here?

Here is a sample project with all of the code we’ve developed in this tutorial series.

At this point I’d recommend reading Apple’s Mac App Programming Guide, or having a look at some of the samples provided by Apple.

You can also try to add different kinds of controls or new functionality to the application. For instance, how about writing the model to a file, and asking the user where to store it using a file save panel? Or maybe add the ability to search a bug in your list, using a search field control?

I hope you enjoyed making this Mac version of the ScaryBugs app! If you have any questions or comments or would like to see more Mac tutorials on this site in the future, please join the forum discussion below!


This is a post by iOS Tutorial Team Member Ernesto García, a Mac and iOS developer founder of CocoaWithChurros.

Ernesto García
Ernesto García

Ernesto is a Mac and iOS developer from Spain. After 16+ years of experience developing Enterprise applications, now he is an indie developer who focuses on creating great mobile and desktop applications. He's the founder of CocoaWithChurros where he develops Apps for clients as well as his own. You can also find him on Twitter or Github.

User Comments

22 Comments

[ 1 , 2 ]
  • hi ernesto,

    thank you for your reply. yes that is what i am asking.
    i still not able to find the file. is there a way in the build setting or somewhere i can set where I want to store this files?
    or is there any other way i want to run my app fresh again?

    thanks :)
    harsha
  • harsha wrote:hi ernesto,

    thank you for your reply. yes that is what i am asking.
    i still not able to find the file. is there a way in the build setting or somewhere i can set where I want to store this files?
    or is there any other way i want to run my app fresh again?

    thanks :)

    So, you're using NSUserDefaults them?

    in that case, you need to delete the preferences file. It's located in your home folder, ~/Library/Preferences/
    And its name is the bundle identifier you used in your app.

    For instance, if your app's bundle is com.mydomain.greatapp, you should delete the com.mydomain.greatapp.plist file located in that folder.

    If your app is sandboxed, then it's a little bit difference, because your app's data is not located in the user Library folder, but in the app's sandboxed container.
    It should be here.

    ~/Library/Containers/<bundle_id>/Data/Library/<bundle_id>.plist

    where <bundle_id>, as before, is the bundle Identifier you set in XCode

    hope that helps
    Ernesto Garcíaernesto
  • nice! i just done it.
    hijz421
  • Thanks for the tutorial.but its opening in blank window. and saying many bugs like that =

    "property "rating" requires method 'rating' to defined- use@synthesize,
    @dynamic or provide method implementation in this class implementation!,"
    Content rectangle not entirely on screen with the menu bar (May not be completely visible for all screen resolutions and configurations)
    Property 'window' requires method 'window' to be defined - use @synthesize, @dynamic or provide a method implementation in this class implementation
    Property 'rating' requires method 'rating' to be defined - use @synthesize, @dynamic or provide a method implementation in this class implementation
    Property 'data' requires method 'data' to be defined - use @synthesize, @dynamic or provide a method implementation in this class implementation
    Property 'bugRating' requires method 'bugRating' to be defined - use @synthesize, @dynamic or provide a method implementation in this class implementation

    is it because of library updates or smt?
    masteryigo
  • masteryigo wrote:Thanks for the tutorial.but its opening in blank window. and saying many bugs like that =

    "property "rating" requires method 'rating' to defined- use@synthesize,
    @dynamic or provide method implementation in this class implementation!,"
    Content rectangle not entirely on screen with the menu bar (May not be completely visible for all screen resolutions and configurations)
    Property 'window' requires method 'window' to be defined - use @synthesize, @dynamic or provide a method implementation in this class implementation
    Property 'rating' requires method 'rating' to be defined - use @synthesize, @dynamic or provide a method implementation in this class implementation
    Property 'data' requires method 'data' to be defined - use @synthesize, @dynamic or provide a method implementation in this class implementation
    Property 'bugRating' requires method 'bugRating' to be defined - use @synthesize, @dynamic or provide a method implementation in this class implementation

    is it because of library updates or smt?


    Hi

    It seems you must be using an older version of Xcode.
    The tutorial uses automatic properties sysnthesis, which requires at least Xcode 4.4

    If you don't want to upgrade your Xcode, the tutorial will compile just fine by adding synthesize sentences for all the properties.
    For instance, @synthesize rating;
    Ernesto Garcíaernesto
  • Hi, first of all, thanks for this great tutorial!

    I'm still a beginner, and I've run into a problem, but I think you know the answer.

    I have completeted the tutorial and posted it as an app on my desktop. When I change ratings or add new bugs to the program it works fine. When I close the app and reopen it, the app has been reset. There are again only 4 bugs with the same rating as they were given in the beginning.

    Any clues?
    Chraasen
  • Chraasen wrote:Hi, first of all, thanks for this great tutorial!

    I'm still a beginner, and I've run into a problem, but I think you know the answer.

    I have completeted the tutorial and posted it as an app on my desktop. When I change ratings or add new bugs to the program it works fine. When I close the app and reopen it, the app has been reset. There are again only 4 bugs with the same rating as they were given in the beginning.

    Any clues?


    Hi Chraasen

    That is the expected behavior. The tutorial does not save the bugs information, so when you close and reopent it, that information is gone.
    If you need that information to persist, you'll need to save it.

    A simple way would be to save it in the user defaults, by using this class : NSUserDefaults , or maybe use NSCoding.
    you can find a great tutorial about NSCoding here, http://www.raywenderlich.com/1914/how-to-save-your-app-data-with-nscoding-and-nsfilemanager
    The tutorial is for iOS , but the API is exactly the same in OSX, so you should be able to use it in the Mac App. You may need to change the path, and the way NSImage is saved (which is similar, but a bit different to UIImage).

    best regards
    Ernesto
    Ernesto Garcíaernesto
[ 1 , 2 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in May: Procedural Level Generation in Games with Kim Pedersen.

Sign Up - May

Coming up in June: WWDC Keynote - Podcasters React! with the podcasting team.

Sign Up - June

Vote For Our Next Book!

Help us choose the topic for our next book we write! (Choose up to three topics.)

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

... 55 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • Sonic Zhao

... 38 total!

Subject Matter Experts

  • Richard Casey

... 4 total!