Apr 302011
 

We are proud to announce a BETA release of the DYMO Label Mobile SDK for iOS. The DYMO Label Mobile SDK for iOS is a complete toolset containing libraries, documentation, and samples that make it easy to add label printing to any iOS app. All DYMO LabelWriter and DYMO LabelManager printers are supported. Using the DYMO Label Mobile SDK in combination with DYMO Label Web SDK allows creating both native and web-based apps for iOS.

Please Note

This release is a BETA and is not supported by DYMO. It has not been extensively tested outside DYMO and should be used for developer testing only, NOT for production software releases.

Architecture Overview

Before going into details let’s first look at the high level architecture for the Mobile SDK to understand how it works. It is similar to the DYMO Web SDK with one difference: instead of using a DYMO Label Javascript library, a native binary library is compiled into an iOS app.

Pasted Graphic 1

The major piece here is a computer that has a DYMO printer plugged-in and DYMO Label software installed. DYMO Label software contains a service called DYMO Label Proxy that allows communication between a iOS device and the printer.

Notes:

  • right now DYMO Label Proxy is available for Windows only. So, a printer must be connected to a Windows machine, or be available from Windows machine that has DYMO Label Proxy installed (see below).
  • the printer itself does not have to be connected directly to the computer running DYMO Label Proxy. The printer has to be ACCESSIBLE from that machine. This means that the printer might be really connected to a different machine and be a “shared” network printer. Or the printer might be connected to a DYMO Print Server and be “installed” on the computer using a RAW TCP/IP port. This setup might be used to simplify installation (see below).
  • the local network is used for communications between the proxy service and the mobile device. That means that on the mobile device the Wi-Fi should be turned on and the device should be connected to the network. Using just 3G is not enough.

Installation

SDK Installation

SDK is available here. It is a zip file, just extract it to any folder. Inside there are following folders:

  • docs– SDK documentation. Contains API reference documentation in two formats:
    • html to be viewed using web browser.
    • com.dymo.label.framework.docset to be integrated with XCode. To install the docset copy it to ~/Library/Developer/Shared/Documentation/DocSets folder.
  • include – header files.
  • lib – static libraries to link with.
  • Samples – sample apps.

Samples were tested in XCode 4.0.2 and XCode 3.2.6 for iOS SDK 3.2 – 4.3.

DYMO Label Proxy Installation

The installation is fairly simple. Just install DYMO Label software, that’s it. DYMO Label Proxy service will be installed and run as a part of the installation. DYMO Label Proxy is a Windows service running on port 8631 by default. Because of that there are couple of considerations that must be taken into account:

  • DYMO Label Software will configure Windows Firewall to open the port 8631 for inbound requests. If a different firewall software is installed, you have to configure it manually.
  • The port number may be changed from the default one. Either use the configuration utility included or specify the port manually in the service’s application .config file. In any case don’t forget to update the firewall rules to open the port. Otherwise clients will not be able to connect to the service.
  • The final release to the public will add the ability to select the port number and autostart options for the service during installation.

DYMO Label Proxy Configuration

To configure the service use DYMOLabelProxyServiceConfiguration.exe utility. It lets you change the port number the service is listening to as well as stop/start/restart the service. In addition the utility displays a list of urls that might be used to connect to the service.

image_thumb

Changing Port Number

To change the port number enter a new value into “Port Number” field and click “Apply” button. Right now the service is not restarted automatically, so don’t forget to restart it.

Service Control

You can start/stop/restart the service from within the configuration utility. Alternatively the standard “Services” panel of the  “Computer Management” tool can be used.

Service ip-address

Usually the SDK is able to discover the service on the network automatically using Bonjour. But sometimes Bonjour does not work. One common case is when the service and iOS device are on different subnets, e.g. the mobile device is connected to a “wireless” subnet and the service to a “wired” subnet. This is not a problem with the DYMO service, it is how Bonjour works in its default configuration. There are solutions for this problem, but the detailed descriptions are beyond the scope of this post. Some ideas:

In this case it is necessary to know the service’s ip-address to be able to connect to it from iOS device (see below). The configuration utility can help here as well. Service’s ip-address(es) is displayed in Service URIs list.

API Overview

The SDK contains two complete samples that demonstrate most of the functionality the Framework provides. Here we will look at the API from the point of tasks that are necessary to perform to print a label. To print a label the following task usually have to be performed:

  1. discover a printer to print the label on
  2. load label layout
  3. set label data
  4. perform actual printing
  5. Monitor printing progress

Note: these tasks might be performed in a different order, e.g. printer discovery might be performed after the label is prepared for the printing. Though usually printer discovery should be performed first, because it can take some time.

Discover printers

Before something can be printed it is necessary to know on which printer it will be printed. DlfPrinters class is used to perform this task. Printer discovery is an asynchronous operation. The reason is that it requires network communications and various delays and timeouts might happen during the process. So, the Framework follows usual iOS SDK approach, and a delegate object is used to provide a way of receiving async notifications of the discovering progress. The delegate protocol is DlfPrintersDelegate. DlsPrintersDelegate defines two methods, one called when a new printer or printers are discovered, another when some sort of failure happened. So, create an object that implements DlfPrintersDelegate protocol, and initialize DlfPrinters instance by passing the delegate object into initWithDelegate: method. It is possible to reassign the delegate later if necessary using delegate property. To start discovering call refresh method. This method is asynchronous and returns almost immediately. When a printer is found the delegate’s method – printers:(id<IDlfPrinters>)sender didFindPrinters:(NSArray*)printers will be called. Here is a code snippet to demonstrate that:

@interface PrintMeThatLabelAppDelegate :
    NSObject <UIApplicationDelegate, DlfPrintersDelegate>
{
@private
    DlfPrinters* printers_;
}

@end

@implementation PrintMeThatLabelAppDelegate

- (void) applicationDidBecomeActive:(UIApplication*)application
{
    printers_ = [[DlfPrinters alloc] initWithDelegate:self];
    [printers_ refresh];
}

- (void) applicationWillResignActive:(UIApplication*)application
{
    [printers_ release];
}

- (void) printers:(id<IDlfPrinters>)dlfPrinters didFindPrinters:(NSArray*)printers
{
    for (id<IDlfPrinter> printer in printers)
        NSLog(@"%@", printer.name);
}

- (void) printers:(id<IDlfPrinters>)printers didFailWithError:(NSError*)error
{
    NSDictionary* userInfo = error.userInfo;
    NSString* s = [[[NSString alloc] initWithFormat:@"Unable to contact '%@' (%@)",
        [userInfo objectForKey:@"printerUri"],
        [userInfo objectForKey:@"printerLocation"]] autorelease];
    NSLog(@"%@", s);
    UIAlertView* alert = [[[UIAlertView alloc]
        initWithTitle:@"Error"
        message:s delegate:nil
        cancelButtonTitle:@"OK"
        otherButtonTitles:nil] autorelease];
    [alert show];
}

@end

Load Label Layout

A label layout specifies what will printed on a label. The layout contains ‘label objects’ – text, address, barcode, image, etc. Each object has a ‘reference name’, so the object can be referenced programmatically from the SDK. Usually DYMO Label software is used to create a layout and save it as xml file. Because the layout is serialized as a xml document, the layout can be created or modified using any xml editor or even programmatically. This and that blog posts describe label format in great details. To load label layout use [DlfLabel labelWithXml:] method. Usually we will load the layout from a resource file in the app bundle. Something like this:

// obtain a reference to IDlfLabel by loading a label from a file
NSString* labelFile = [[NSBundle mainBundle] pathForResource:@"MyLabel" ofType:@"label"];
NSString* xml = [NSString stringWithContentsOfFile:labelFile encoding:NSUTF8StringEncoding error:NULL];
id<IDlfLabel> label = [DlfLabel labelWithXml:xml];

Set Label Data

Label Layout can already contain data to be printed, e.g. an address. this might be a case when a label is generated dynamically on the server. But usually we will need to set data programmatically based on user’s input. This can be done using two different methods.

The first method allows printing of one label only. Use [IDlfLabel setText:forObject:] method to set text data of Address, Text, Barcode, and CircularText objects. Use setImage:forObject: to set image data for Image objects.

The second method is more universal, and allows printing multiple labels at once. It is implemented using a “label set” concept. Label set is similar to a recordset, a database table. Label set consist of a set of “label records”. Each label record represents data to be printed on one label. Label record itself is conceptually is a dictionary (associative array), where dictionary keys are label object names, and dictionary values are label object data. To manipulate a label set use DlfLabelSet class. Use addRecord method to add a record into a label set. To set object data in the record, use IDlfLabelSetRecord methods. There are methods to add text and image data similar to ones above. Also, there is way to specify formatted/styled text data where each character or line can have different formatting (font size and style).

void PrintWithLabelSet(id<IDlfPrinter> printer)
{
    // open a label
    NSString* labelFile = [[NSBundle mainBundle] pathForResource:@"TextLabel" ofType:@"label"];
    NSString* xml = [NSString stringWithContentsOfFile:labelFile encoding:NSUTF8StringEncoding error:NULL];
    id<IDlfLabel> label = [DlfLabel labelWithXml:xml];

    // create a builder object to populate label set
    DlfLabelSet* labelSet = [[[DlfLabelSet alloc] init] autorelease];

    // label #1
    id<IDlfLabelSetRecord> record = [labelSet addRecord];
    [record setText:@"6x7=42" forObject:@"TEXT"];

    //label #2
    record = [labelSet addRecord];
    [record setTextMarkup:@"font family='Arial' size='36'>6x7=<b>42</b></font>" forObject:@"TEXT"];

    // print label
    [label printOn:printer withParams:nil labelSet:labelSet printJobDelegate:nil];
}

Actual Printing

To start printing call – (id<IDlfPrintJob>) printOn:(id<IDlfPrinter>) printer withParams:(DlfPrintParams*) params labelSet:(DlfLabelSet*)labelSet printJobDelegate:(id<DlfPrintJobDelegate>)delegate method of IDlfLabel

We have to provide four parameters:

  • printer – printer to print the label on. The printer instance can be extracted from DlfPrinters instance initialized previously. This is the only required parameter, all other can be omitted.
  • printParams – printing parameters, like number of copies, print quality, etc. This parameter can be null, in witch case defaults will be used.
  • labelSet – a label set contains data to be printed. If omitted, the data contained by label instance will be printed.
  • delegate – a delegate object to receive status messages about printing progress. this might be omitted as well, if print progress monitoring is not important.

This method call is asynchronous as well. Right after all necessary data are assembled and printing is started the method returns. Printing progress monitoring can be done using IDlfPrintJob instance returned by the printOn: method (see below).

Print Progress Monitoring

IDlfPrintJob instance returned by printOn:withParams:labelSet:printJobDelegate: call can be used to monitor print progress. Periodically call getStatus: method to retrieve current status information. Note: currently only this “polling” schema is supported; in the future we might add “pushing” data from the printer when it status has changed as well. getStatus: is async call as well; when status data has retrieved from the printer, the delegate method printJob:(id<IDlfPrintJob>) printJob didGetStatus:(id<IDlfPrintJobStatus>) printJobStatus is called. Here is an example:

@interface PrintMeThatLabelAppDelegate : NSObject <UIApplicationDelegate, DlfPrintersDelegate, DlfPrintJobDelegate>
{
@private
    DlfPrinters* printers_;
    id<IDlfPrinter> currentPrinter_;
}

@end

@implementation PrintMeThatLabelAppDelegate

- (IBAction) printAction:(id)sender
{
    // load label
    NSString* labelFile = [[NSBundle mainBundle] pathForResource:@"MyLabel" ofType:@"label"];
    NSString* labelXml = [NSString stringWithContentsOfFile:labelFile encoding:NSUTF8StringEncoding error:NULL];
    id<IDlfLabel> label = [DlfLabel labelWithXml:labelXml];

    // specify label data
    DlfLabelSet* labelSet = [[DlfLabelSet new] autorelease];
    id<IDlfLabelSetRecord> record = [labelSet addRecord];
    [record setText:@"Hello, World!" forObject:@"TEXT"];
    id<IDlfPrintJob> printJob = [[label printOn:currentPrinter_ withParams:nil labelSet:labelSet printJobDelegate:self]
                                retain];

    // start job monitoring
    [(NSObject*)printJob performSelector:@selector(getStatus) withObject:nil afterDelay:1];
}

- (void) printJob:(id<IDlfPrintJob>) printJob didGetStatus:(id<IDlfPrintJobStatus>) printJobStatus
{
    NSLog(@"got print job status: %@", printJobStatus.statusMessage);

    // check status - if not completed then schedule another run, otherwise done with the print job
    DlfJobStatusId statusId = printJobStatus.statusId;
    if (statusId == kDlfPrintJobStatus_ProcessingError || statusId == kDlfPrintJobStatus_Finished)
        [printJob release];
    else
        [(NSObject*)printJob performSelector:@selector(getStatus) withObject:nil afterDelay:1];
}

@end

Setup XCode Project for Using with the SDK

To be able to use SDK libraries to create iOS app a few steps have to be done.  The main (and the only one at the moment) library in the SDK is a static library libDymoLabelFramework.a located in the lib SDK folder. To be able to compile and link  with the library include path to the headers should be specified, as well as the lib path to the library.

  • add a path to header files. Header files are located in DymoSDK/include/DymoLabelFramework folder. So, add the path to SDK/include into “Header Search Paths” Build Settings of the project. After that the headers can be included into *.h or *.m file by using #import “DymoLabelFramework/DymoLabelFramework.h”. Alternatively the include/DymoLabelFramework folder might be copied into the project source folder.  
  • add path to the location of libDymoLabelFramework.a  (DymoSDK/lib) to the “Library Search Paths”
  • add the library libDymoLabelFramework.a into the project. Add it to “Link Binary  With Libraries” group of “Build Phases” tab.libDymo

Conclusion

DYMO Label Mobile SDK for iOS  provides a simple way to add label printing capabilities to any iOS app. Along with DYMO Label Web SDK developers can use DYMO printers from their web-based applications or  from native apps.