TweetFollow Us on Twitter

iPhone Productivity Applications, Part I

Volume Number: 25 (2009)
Issue Number: 03
Column Tag: iPhone

iPhone Productivity Applications, Part I

Developing applications that manage complex data

by Rich Warren

(Ed note: Parts I and II were actually printed out of order. Click here for Part II.)

Productivity Applications

My Last article, "Built for iPhone 2.0" described the three general categories of iPhone applications. Utilities provide a single view of the data with little or no interaction. Productivity applications manage and organize complex data. While immersive applications use the entire screen to provide a rich, interactive view. We then looked at the major tools used to build an iPhone application, and stepped through the development of a simple utility app.

This time, we will turn our attention to productivity. Productivity applications focus on managing and organizing complex data. Developers typically refrain from customizing the interface. Instead, they focus on creating a clean, streamlined solution that allows the user to move through the data in an unobtrusive and intuitive manner.

Note: productivity applications are not necessarily limited to traditional productivity-oriented tasks, like ToDo lists and calendars. Any application that focuses on data and organization may benefit from the productivity style. Mail is a perfect example of a productivity application. It allows you to manage a number of incoming and outgoing messages from a variety of different email accounts. Many third-party social networking applications also fall into this category. Even the iPod application is largely a data-driven, productivity app.

Productivity applications use three main tools to organize and interact with your data: UITabBarControllers, UINavigationControllers and UITableViews.

UITabBarControllers let the user easily switch between different tasks or different groups of data. For example, the clock application lets the user choose between the different time-related tasks: World Clock, Alarm, Stopwatch and Timer. The iPod app, on the other hand, lets the user select between different views of the data: Album, Artist, Genre, Song, Playlists, Videos, etc. Some applications even mix tasks and data groups. The App Store displays three data groups: features, categories and top 25, while letting the user access the search and updates tasks.

The tab bar can support any number of tab items. However, it will only show five tabs at a time. UITabBarController automatically manages any extra tab items by providing a more... button, and letting the user customize the items that appear on the tab bar.

Next, UINavigationController lets users move from general to more specific views. This controller maintains a stack of views. When a new view is added to the top of the stack, that view slides in from the right, while the old view slides off the left. When the top view is popped from the top of the stack, it slides back off the right, and the previous view returns from the left. This provides a very intuitive interface for moving through hierarchical data.

While both UITabBarController and UINavigationController manage navigation between views, UITableView handles the actual display of data. Tables are often used to present lists, for example, the list of email accounts, the list of mailboxes within a single account, or even the list of messages within a single mailbox. UITableView displays a single column of cells. Each cell within the table can be quite complex, combining images, icons and formatted text. But, in general, the pattern holds. Each entry represents a single selection from a list of similar items.

A productivity application typically combines some or all of these tools into a user interface that naturally matches the application's data. In general, UITabBarController provides the coarsest level of organization, defining our broadest groups. Within each group, UINavigationControllers allows the user to move from the most general to the most specific information, while UITableViews display the information available at each level of detail.

Note: Applications may have multiple UINavigationControllers or UITableViews; however, they should only have a single UITabBarController. Additionally, if you use a tab bar, then UITabBarController should always be the root controller for your application. You can add UINavigationControllers to a UITabBarController, but you should never add a UITabBarController to a UINavigationController.

Gas Tracker

For the rest of this article, we will build a simple productivity application. Given the state of our economy and the recent spike in gas prices, I wanted a tool that would help me track the amount of money I'm wasting on my car.

In many ways, this is an ideal iPhone application. A desktop version just wouldn't work. I'm nowhere near my computer when I fill up my tank. Sure, I've tried carrying a notepad in my car to do these calculations manually, but I always lose the pen under the seat. However, if I'm wearing pants, I have phone with me.

Additionally, the tasks are appropriate for a mobile application. Each interaction is short and simple - perfect for managing data on the go.

Creating the Project

OK, let's get started. Open Xcode. Select File ... New Project.... This brings up the New Project window. Project templates are grouped into categories shown in the leftmost column. Select the iPhone OS Application group.

Apple provides a number of project templates, each pre-loaded with the initial framework for a particular style of application. Since we will use a tab bar to manage the lowest levels of our view hierarchy, we should select Tab Bar Application and press the Choose button. In the next panel, name the application GasTracker, select a path and press Save.


Select the Tab Bar Application Template

Like all Xcode templates, the Tab Bar Application provides a great deal of functionality before we even touch the code. Go ahead and build and run the application. As you see, it creates a tab view that allows us to toggle between two different views.


The code itself starts with the GasTrackerAppDelegate. As with most Cocoa applications, the app delegate manages many of the high-level details. Currently, it simply creates the tab bar controller when the application launches, then releases the controller and window when the application closes.

The delegate also acts as a UITabBarControllerDelegate. There are two commented-out methods for modifying the tab bar's behavior. tabBarController:didSelectViewController: is called whenever the user presses a button on the tab bar. The application also calls tabBarController:didEnd-CustomizingViewControllers: whenever the user edits the buttons on the toolbar. We won't use either of these methods in this application, but they may be useful in your own projects.

The only other class is FirstViewController. Like the app delegate, the initial implementation does not do much, but it has a number of method stubs we could use to customize our view's behavior. We will examine these in more detail later.

There are also two nib files. MainWindow.xib defines the application's Window and Tab Bar Controller. The tab bar's views can either be defined within the Tab Bar Controller, or placed in separate nib files. In this template, the first view is defined within the controller. Open the MainWindow.xib , and then open the Tab Bar Controller. Click on the First button. You will see the layout directly.

The second view is defined in its own nib, named (not surprisingly) SecondView.xib. Click on the tab bar's Second button, and you will see a grayed-out box in the center of the view, with a link to SecondView.xib. Double click the link, and the second view's nib will open automatically.

While the generated code demonstrates a few useful techniques, the initial files are not very useful. I think it's better to just start from a clean slate. First, let's handle the simple part. In Xcode, delete FirstViewController.h, FirstViewController.m, and SecondView.xib. Go ahead and move them to the trash when prompted.

Now for the trickier part. In Interface Builder, open MainWindow.xib, and click on the Tab Bar Controller from the Interface Builder Documents.


Interface Builder Documents

Make sure the Attributes Inspector is open (either through the Tools menu, or by clicking on the Inspector's Attributes tab). You should see a list of view controllers that have been added to the tab bar. Select First, and then click the minus button. It should be deleted from the list. Do the same for Second.


Now let's add our views to the tab bar. We can set both the name and the class (or at least the super class) in this table. Press the plus button. Name this view History, and set the class to Navigation Controller. Now add five more views. Name these MPG, Gas Price, Cost/Mile, Cost/Month and Cost/Day. Leave the classes set to View Controller.

Note: You should be a little careful with the button titles. If they are too long, the labels may appear cramped or even overlap. Also, the size shown in Interface Builder is different from the size in the actual app. Build and run the application to test the label's true size. According to my tests, Cost/Month is OK, but Cost Per Month was too long.


Now look at the Tab Bar Controller window again. Notice, if you click the button once, the window will display the view associated with that button. All the views are currently grayed out. Additionally, the Inspector will show the view controller settings for that view. Double click the button, and the inspector shows the settings for the tab bar item itself.

Building Controllers and Nibs

Before we can do any more work in Interface Builder, we need to create our nibs and controller classes. Let's start by adding the controllers. Back in Xcode, right click on the Classes folder in the Groups & Files view. Select Add ... New File.... In the New File window, make sure Cocoa Touch Classes is selected, and then double click on NSObject subclass. In the next screen, name it HistoryNavigationController.m and click Finish. Do the same for StatsViewController.m, but this time, select the UIViewController subclass.


Adding a UIViewController Subclass

NSObject may seem like a strange choice for our HistoryNavigationController, but we don't need the method stubs provided by the UIViewController template, and we're going to change the super class anyway. So, open HistoryViewController.h and make the changes shown below:

HistoryNavigationController.h

Initial interface for the HistoryNavigationController class.
#import <UIKit/UIKit.h>
@class Model;
@interface HistoryNavigationController : UINavigationController {
   IBOutlet Model *model;
}
@property (nonatomic, retain) Model *model;
@end

We've made two small changes. As we already indicated, we've changed the super class to UINavigationController. Second, we've added a Model property, including a forward declaration of the Model class, and a model instance variable. The model is also declared as an IBOutlet, which allows us to connect it to the controller using Interface Builder. What's Model? Hold that question; we'll get to that in a bit.

While we're here, take a closer look at the property definition. We are using both the nonatomic and the retain keywords. The first keyword is an easy choice. Most iPhone properties should be declared nonatomic. They typically do not need to be thread safe, and using nonatomic accessors improves performance.

The second keyword is more of a design choice. By default, properties are set to simply assign incoming values to the instance variable. That may be appropriate if you can guarantee that the object in question will be retained somewhere else in your application. In our case, the model object will be instantiated by our nib file.

In iPhone OS, the nib file autoreleases all objects it creates. However, if you connect an object to an IBOutlet, it will call setValue:forKey: which will use the appropriate setter method. For our model, it will use the setter defined by the property, so it will be retained. Additionally, if you don't provide an explicit setter, the object would be retained automatically.

Note: The rules defining how nibs retain objects differ from Mac OS X to iPhone OS. In both cases, Apple recommends that you manage these objects through an IBOutlet, and create an appropriate setter that explicitly retains the object. For more information, see the Resource Programming Guide, which can be found either in Xcode's help documentation or online at http://developer.apple.com.

Ok, let's look at the implementation. Open HistoryNavigationController.m. We need to make three small changes here. Import Model.h, synthesize the model property, and release the model as shown below.

HistoryNavigationController.m

Initial implementation for the HistoryNavigationController class.

#import "HistoryNavigationController.h"
#import "Model.h"
@implementation HistoryNavigationController
@synthesize model;
- (void)dealloc {
    [model release];
    [super dealloc];
}
@end

Now add the model property to the StatsViewController. It follows the same pattern: forward declaration, instance variable and property declaration in StatsViewController.h; import, synthesize and release in StatsViewController.m. I will leave the actual typing up to you.

While you're there, you'll probably notice that StatsViewController.m contains a number of method stubs. These handle a range of different events: custom initialization, rotation, and low memory warnings. We'll look at these settings in more detail in part two.

For now, let's build our nib. At this point, we'll just create a single nib for our stats view controller. Right click on the Resources folder and select Add ... New File.... Make sure User Interfaces is selected, and double-click View XIB. Name it StatsView.xib.

Open StatsView.xib. In the Document Window, select File's Owner. Open the Identity Inspector, and change the class to StatsViewController. Next, connect the File's Owner's view outlet to the View object in the Document Window. You can do this by right clicking on the File's Owner icon. A black and gray table will appear. Click in the circle at the end of the view row, then drag to the View icon. You can now save and close the nib file. We will edit the layouts later.


Connecting the File's Owner's view Outlet

Note: Nibs are traditionally placed in the Resources group; however, you do not have to place them there. The groups are only used within Xcode to organize and manage the source files. They have no relationship to the actual layout of the project's files on disk. In fact, you can create your own groups to further improve the project's organization. If you download the source code for this project from ftp.mactech.com, you will see that I've created a number of additional subgroups within the Classes group.

Ok, now let's link our tabs to these nib files. Make sure MainWindow.xib is open. Select the Tab Bar Controller icon in the Document Window. This will open the Tab Bar Controller view, if it isn't open already. The tab bar displays the six views that we just added. Single click on the History tab. The Attributes Inspector should be labeled as Navigation Controller Attributes. If not, try clicking on another tab, then single clicking on the History tab again.


The Nib Name attribute here would be for the Navigation Controller itself. We won't use a nib for this controller, so just leave this entry blank. We will eventually modify the navigation controller's layout directly in the Tab Bar Controller view. Switch to the Identity Inspector (either through the Tools menu, or by clicking on the Inspector's Identity tab), and change the class to HistoryNavigationController.



Now, single click on the MPG tab. This time, we should set the nib name to StatsView and set the class to StatsViewController. Repeat this for all the remaining tabs.



Now let's set the icons for each tab. History is the easiest. Make sure the Attribute Inspector is open. Double click on the history tab to bring up the Tab Bar Item Attributes. Change Identifier to History. This will set both the tab title and the icon to the built-in History tab item.


Note: Each of the built-in icons is automatically linked with a title. You cannot use the icon without using the corresponding title. If you want a custom title, you must use a custom icon.

For the other tabs, we will customize the icons and titles. Tab bar icons are simply 30-pixel by 30-pixel PNG image. Notice, however, that the image color does not matter. The tab bar uses the alpha value of each pixel to create a monochrome icon. It displays the alpha channel as blue in the tab bar, and as black in the More... list.

To create these icons, I find it easiest to use a paint program that supports transparencies. Draw the icon in black against a transparent background. You can even set the transparency of the ink to draw in grayscale. Then save the image as a PNG.

30 x 30 Icon Art, Black with a Semi-Transparent Gray Background


Once you have an icon, add it to your project. In Xcode, select Project ... Add to Project.... Navigate to the PNG file and click Add. In the next window, be sure to check Copy items into destination group's folder (if needed).

Now, back in Interface Builder. Double click on the tab in question. Identifier should say Custom. Select your icon's name from the Image combo box. Repeat for each tab.


OK, I know...I know...I've gone through a lot of Interface Builder commands rather quickly. Hopefully, I've given you enough pointers to get by, but a complete tutorial on Interface Builder is really beyond the scope of this article. If you have any questions or difficulties, check out Apple's Interface Builder User Guide. That can be found either in Xcode's developer documentation, or online at http://developer.apple.com.

M is for Model

Apple really pushes the Model View Control pattern (MVC to the cool kids). I won't go into a full description of MVC here. If you're not familiar with the concept, there are plenty of great explanations on the web. I'll just say that I agree with Apple. MVC is a good idea. [Ed. Note: For a review of MVC, see Dave Dribin's "Road to Code" in the August, 2008 issue]

We've already seen two of the three main players. We've just built a number of views and controllers. We've linked them together. All that with only a few lines of code.

But now things get a little weird. While Cocoa provides clear support for the controllers and views, models aren't so obvious, at least not on the iPhone. Here's the problem. Most of our controllers will need a connection to our model. Some of these controllers may be instantiated pragmatically, letting us pass the model to them directly, but many are automatically created through the nib files.

There are a number of ways to work around this problem. Many people simply instantiate their model object in the app delegate, and then add a property to the delegate. They can then access the property anywhere in their project, using the application singleton.

Using the UIApplication Singleton

Add your model to the application delegate as a property. You can access the application Singleton anywhere in your project. From the application, you can access the delegate, and from the delegate you reach your model.

UIApplication *app = [UIApplication sharedApplication];
id delegate = app.delegate;
id modelObject = [delegate model];

This works, and it is reasonably simple. However, there are a number of reasons I don't like it. First, singleton classes have many of the same problems as global variables. Second, we're programming against a particular application delegate's implementation, not the UIApplicationDelegate protocol. Third, from a pure Object Oriented standpoint, managing the model seems outside the application delegate's role. Finally (and most importantly), it just feels kludgy.

Alternatively, we could create a singleton object to explicitly manage our model. While this feels better in many ways, I still prefer to avoid Singletons where possible. Besides, it may be a bit heavy handed for this application. Though, I will admit, it may be the cleanest solution for some of the more-complicated projects.

If possible I'd like to connect the model the same way we connected the views and controllers, using Interface Builder. Fortunately, in this application we can create a single Model object in our MainWindow.xib, and connect it to all the relevant controllers. This may not always be possible. In more complex projects, you may need to pass an external object into a nib as a proxy object. These objects are then set when you load the nib file using NSBundle's loadNibNamed:owner:options: method. For more information, check out the NSBundle UIKit Additions, Resource Programming Guide and Interface Builder User Guide.

The first step is to build our model. Unfortunately, that is outside the scope of this article. Instead, you can simply grab the necessary code from ftp.mactech.com. You will need both the Model and the Entry classes. Add these to your project.

Note: For this project, our model knows how to automatically load any saved data when it is instantiated. It will also save itself whenever the user makes any changes. This greatly simplifies our controller and delegate code. Check out Model.m for more details.

Now let's add the object to our nib. Open MainWindow.xib. Drag an Object controller from the Library to the Document Window. In the Identity Inspector, set the Object's class to Model. Now we have to make our connections. Single click on each tab to select the tab's controller, then right click on the tab. Connect the tab's model outlet with the model object. That's it. We've linked our model, views and controllers.

Configuring Tab Customizability

As I said earlier, the tab control can only display up to five icons at a time. If you have more than five view controllers, the tab controller only displays four of them and a More... button. Clicking on the More... button brings up a table with all the additional controllers. The user can then select a controller from the table, or they can click on the edit button and customize the contents of the tab bar.

However, not all tab items are customizable. For example, the user cannot replace the More... button. In fact, UITabBarController maintains an array of customizable controls. If a controller is in that array, then it can be moved or removed from the tab bar. By default, all the tab bar's view controllers are automatically placed in the customizeableViewController array.

In our program the History view will act as the main interface for managing our data. It should always be available, therefore we must remove it from the customizeableViewController array. We could subclass UITabBarController, but that's probably overkill for this application. Instead, we can access the tab bar controller from within our application delegate.

Open GasTrackerAppDelegate.m, and modify the applicationDidFinishLaunching: method, as shown below:

applicationDidFinishLaunching:

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    
    // Add the tab bar controller's current view 
    // as a subview of the window
    [window addSubview:tabBarController.view];
   
      
    // Only the StatsViewControllers are customizeable
    NSMutableArray *customizeable = [[NSMutableArray alloc] init];
    for (id controller in tabBarController.customizableViewControllers) {
      
        if ([controller isKindOfClass: [StatsViewController class]]) {
            [customizeable addObject:controller];
        }
    }
   
    tabBarController.customizableViewControllers = customizeable;
    [customizeable release];
}

Basically, this method creates a new array that only contains subclasses of StatsViewController, then it assigns that array to the UITabBarController's customizeableViewControllers property.

OK, a little bit of housecleaning left. Be sure to import StatsViewController.h at the top of GasTrackerAppDelegate.m.

#import "GasTrackerAppDelegate.h"
#import "StatsViewController.h"

Looking Forward:

So far, we've built the application's skeleton and set up the tab view and the model. The application compiles without any warnings. When you run the app, you can switch from tab to tab or customize the tab bar. Of course, the various views don't do much yet.

This project will continue in Part 2. In particular, we will focus on setting up the navigation controller and our table views. We will also add a view for entering data, and create custom classes for each of the stats views. Once that's done, we'll have a fully functional productivity application.


Rich Warren lives in Honolulu, Hawaii with his wife, Mika, daughter, Haruko, and his son, Kai. He is a software engineer, freelance writer and part time graduate student. When not playing on the beach, he is probably writing, coding or doing research on his MacBook Pro. You can reach Rich at rikiwarren@mac.com, check out his blog at http://freelancemadscience.blogspot.com/ or follow him at http://twitter.com/rikiwarren.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Go from lowly lizard to wicked Wyvern in...
Do you like questing, and do you like dragons? If not then boy is this not the announcement for you, as Loongcheer Game has unveiled Quest Dragon: Idle Mobile Game. Yes, it is amazing Square Enix hasn’t sued them for copyright infringement, but... | Read more »
Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »

Price Scanner via MacPrices.net

Limited-time sale: 13-inch M3 MacBook Airs fo...
Amazon has the base 13″ M3 MacBook Air (8GB/256GB) in stock and on sale for a limited time for $989 shipped. That’s $110 off MSRP, and it’s the lowest price we’ve seen so far for an M3-powered... Read more
13-inch M2 MacBook Airs in stock today at App...
Apple has 13″ M2 MacBook Airs available for only $849 today in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty is included,... Read more
New today at Apple: Series 9 Watches availabl...
Apple is now offering Certified Refurbished Apple Watch Series 9 models on their online store for up to $80 off MSRP, starting at $339. Each Watch includes Apple’s standard one-year warranty, a new... Read more
The latest Apple iPhone deals from wireless c...
We’ve updated our iPhone Price Tracker with the latest carrier deals on Apple’s iPhone 15 family of smartphones as well as previous models including the iPhone 14, 13, 12, 11, and SE. Use our price... Read more
Boost Mobile will sell you an iPhone 11 for $...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering an iPhone 11 for $149.99 when purchased with their $40 Unlimited service plan (12GB of premium data). No trade-in is required... Read more
Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16″ and 14″ MacBook Pros along with 13″ and 15″ MacBook... Read more
Every model of Apple’s 13-inch M3 MacBook Air...
Best Buy has Apple 13″ MacBook Airs with M3 CPUs in stock and on sale today for $100 off MSRP. Prices start at $999. Their prices are the lowest currently available for new 13″ M3 MacBook Airs among... Read more

Jobs Board

Solutions Engineer - *Apple* - SHI (United...
**Job Summary** An Apple Solution Engineer's primary role is tosupport SHI customers in their efforts to select, deploy, and manage Apple operating systems and Read more
DMR Technician - *Apple* /iOS Systems - Haml...
…relevant point-of-need technology self-help aids are available as appropriate. ** Apple Systems Administration** **:** Develops solutions for supporting, deploying, Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.