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

Ride into the zombie apocalypse in style...
Back in the good old days of Flash games, there were a few staples; Happy Wheels, Stick RPG, and of course the apocalyptic driver Earn to Die. Fans of the running over zombies simulator can rejoice, as the sequel to the legendary game, Earn to Die... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Netflix Games expands its catalogue with...
It is a good time to be a Netflix subscriber this month. I presume there's a good show or two, but we are, of course, talking about their gaming service that seems to be picking up steam lately. May is adding five new titles, and there are some... | Read more »
Pokemon Go takes a step closer to real P...
When Pokemon Go was first announced, one of the best concepts of the whole thing was having your favourite Pokemon follow you in the real world and be able to interact with them. To be frank, the AR Snapshot tool could have done a lot more to help... | Read more »
Seven Knights Idle Adventure drafts in a...
Seven Knights Idle Adventure is opening up more stages, passing the 15k mark, and players may find themselves in need of more help to clear these higher stages. Well, the cavalry has arrived with the introduction of the Legendary Hero Iris, as... | Read more »
AFK Arena celebrates five years of 100 m...
Lilith Games is quite the behemoth when it comes to mobile games, with Rise of Kingdom and Dislyte firmly planting them as a bit name. Also up there is AFK Arena, which is celebrating a double whammy of its 5th anniversary, as well as blazing past... | Read more »
Fallout Shelter pulls in ten times its u...
When the Fallout TV series was announced I, like I assume many others, assumed it was going to be an utter pile of garbage. Well, as we now know that couldn't be further from the truth. It was a smash hit, and this success has of course given the... | Read more »
Recruit two powerful-sounding students t...
I am a fan of anime, and I hear about a lot that comes through, but one that escaped my attention until now is A Certain Scientific Railgun T, and that name is very enticing. If it's new to you too, then players of Blue Archive can get a hands-on... | Read more »
Top Hat Studios unveils a new gameplay t...
There are a lot of big games coming that you might be excited about, but one of those I am most interested in is Athenian Rhapsody because it looks delightfully silly. The developers behind this project, the rather fancy-sounding Top Hat Studios,... | Read more »
Bound through time on the hunt for sneak...
Have you ever sat down and wondered what would happen if Dr Who and Sherlock Holmes went on an adventure? Well, besides probably being the best mash-up of English fiction, you'd get the Hidden Through Time series, and now Rogueside has announced... | Read more »

Price Scanner via MacPrices.net

Apple Studio Display with Standard Glass on s...
Best Buy has the standard-glass Apple Studio Display on sale for $300 off MSRP for a limited time. Their price is the lowest available for a Studio Display among Apple’s retailers. Shipping is free... Read more
AirPods Max headphones back on sale for $449,...
Amazon has Apple AirPods Max headphones in stock and on sale for $100 off MSRP, only $449. The sale price is valid for all colors at the time of this post. Shipping is free: – AirPods Max: $449.99 $... Read more
Deal Alert! 13-inch M2 MacBook Airs on record...
Amazon has 13″ MacBook Airs with M2 CPUs in stock and on sale this week for only $829 in Space Gray, Silver, Starlight, and Midnight colors. Their price is $170 off Apple’s MSRP, and it’s the lowest... Read more
Apple Watch Ultra 2 on sale for $50 off MSRP
Best Buy is offering Apple Watch Ultra 2 models for $50 off MSRP on their online store this week. Sale prices available for online orders only, in-store prices may vary. Order online, and choose free... Read more
Apple introduces the new M4-powered 11-inch a...
Today, Apple revealed the new 2024 M4 iPad Pro series, boasting a surprisingly thin and light design that pushes the boundaries of portability and performance. Offered in silver and space black... Read more
Apple introduces the new 2024 11-inch and 13-...
Apple has unveiled the revamped 11-inch and brand-new 13-inch iPad Air models, upgraded with the M2 chip. Marking the first time it’s offered in two sizes, the 11-inch iPad Air retains its super-... Read more
Apple discontinues 9th-gen iPad, drops prices...
With today’s introduction of the new 2024 iPad Airs and iPad Pros, Apple has (finally) discontinued the older 9th-generation iPad with a home button. In response, they also dropped prices on 10th-... Read more
Apple AirPods on sale for record-low prices t...
Best Buy has Apple AirPods on sale for record-low prices today starting at only $79. Buy online and choose free shipping or free local store pickup (if available). Sale price for online orders only,... Read more
13-inch M3 MacBook Airs on sale for $100 off...
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, along with Amazon’s, are the lowest currently available for new 13″... Read more
Amazon is offering a $100 discount on every 1...
Amazon has every configuration and color of Apple’s 13″ M3 MacBook Air on sale for $100 off MSRP, now starting at $999 shipped. Shipping is free: – 13″ MacBook Air (8GB RAM/256GB SSD): $999 $100 off... Read more

Jobs Board

Relationship Banker *Apple* Valley Main - W...
…Alcohol Policy to learn more. **Company:** WELLS FARGO BANK **Req Number:** R-367184 **Updated:** Wed May 08 00:00:00 UTC 2024 **Location:** APPLE VALLEY,California Read more
Rehabilitation Technician - *Apple* Hill (O...
Rehabilitation Technician - Apple Hill (Outpatient Clinic) - PRN Location: York Hospital, York, PA Schedule: PRN/Per Diem Sign-On Bonus Eligible Remote/Hybrid Read more
LPN-Physician Office Nurse - Orthopedics- *Ap...
LPN-Physician Office Nurse - Orthopedics- Apple Hill Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Read more
Medical Assistant Lead - Orthopedics *Apple*...
Medical Assistant Lead - Orthopedics Apple Hill Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
Licensed Practical Nurse - Womens Imaging *A...
Licensed Practical Nurse - Womens Imaging Apple Hill - Full time Location: York Hospital, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.