Zebra Printer only printing successive multiple labels if tapped by the user - ios

I have a weird issue with Zebra printer. On the big picture, I have codes to fetch items to print one by one from the queue. So if when the printing initiated, and there's 3 items on the queue, the code will loop and fetch the first data in the queue, send it to the printer, and delete the first data in the queue. Sort of like dequeueing.
The problem is, if it's the code that looping and sending the data directly to the printer, the printer will only print the first item. The next item is gone, even though NSLog shows that the printer connection opened, data sent, printing successful, and printer connection closed, for every single item.
But if each time the code print one label, and then the app shows message box like "press OK to print next label", and then the user tap the OK button, it can print the second and the rest of the label after every tap of the button.
I have then tried to emulate this. I've tried to use timer to "push the button programmatically" [btnPrint sendActionsForControlEvents:UIControlEventTouchUpInside], I also use timer to call the function directly, or giving delay to the thread, but none works. It has to be initiated from a button which tapped from human touch. I don't know why.
Here's the code:
// main function to print
-(void) printLabel {
if ([dataToPrint count] > 0) {
[self printWithData:[dataToPrint objectAtIndex:0]];
}
}
-(void)printWithData:(NSString*) data;
{
NSString *zplString = data;
// do something with zplString
NSLog(#"Sending data to printer");
printHandler = [[PrintingHandler alloc] init];
[printHandler setDelegate:self];
[printHandler initialize];
[printHandler printToSerial:bluetoothSerialNumber withData:zplString];
}
// delegate to call if the print is success
-(void) printIsSuccess
{
[dataToPrint removeObjectAtIndex:0];
// in here, I just use sleep code instead of button tap emulation to avoid unnecessarily too long code
[NSThread sleepForTimeInterval:2.0f];
[self printLabel];
}
// this is method inside PrintingHandler class that get called by PrintingHandler (printToSerial:)
-(void) printLabelWithData:(NSString*) zplData toPrinter:(NSString*) serial withSender:(id) sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
// Instantiate connection to Zebra Bluetooth accessory
id<ZebraPrinterConnection, NSObject> thePrinterConn = [[MfiBtPrinterConnection alloc] initWithSerialNumber:serial];
// Open the connection - physical connection is established here.
BOOL success = [thePrinterConn open];
NSError *error = nil;
// Send the data to printer as a byte array.
success = success && [thePrinterConn write:[zplData dataUsingEncoding:NSUTF8StringEncoding] error:&error];
[NSThread sleepForTimeInterval:1.0f];
//Dispath GUI work back on to the main queue!
dispatch_async(dispatch_get_main_queue(), ^{
if (success != YES || error != nil) {
[delegate printFailed];
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
}
else if (success != YES) {
NSLog(#"Print is not success, but no error raised");
[delegate printSuccess];
}
else
{
NSLog(#"Print is success");
[delegate printSuccess];
}
});
// Close the connection to release resources.
NSLog(#"printer connection closed");
[thePrinterConn close];
[thePrinterConn release];
});
}

Sorry, I've found the solution. The problem is it's too fast between opening the connection to the printer and sending data to the printer. I was putting some delay, but I put the delay at wrong position.
So the step to print currently is:
Open bluetooth printer connection
Send data
Delay
Close printer connection
When the correct one should be:
Open bluetooth printer connection
Delay
Send data
Close printer connection
Here I put the answer so this can help other people with same problem.
-(void) printLabelWithData:(NSString*) zplData toPrinter:(NSString*) serial withSender:(id) sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
// Instantiate connection to Zebra Bluetooth accessory
id<ZebraPrinterConnection, NSObject> thePrinterConn = [[MfiBtPrinterConnection alloc] initWithSerialNumber:serial];
// Open the connection - physical connection is established here.
BOOL success = [thePrinterConn open];
NSError *error = nil;
[NSThread sleepForTimeInterval:1.0f]; // this is the important one
// Send the data to printer as a byte array.
success = success && [thePrinterConn write:[zplData dataUsingEncoding:NSUTF8StringEncoding] error:&error];
//Dispath GUI work back on to the main queue!
dispatch_async(dispatch_get_main_queue(), ^{
if (success != YES || error != nil) {
[delegate printFailed];
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
}
else if (success != YES) {
NSLog(#"Print is not success, but no error raised");
[delegate printSuccess];
}
else
{
NSLog(#"Print is success");
[delegate printSuccess];
}
});
// Close the connection to release resources.
NSLog(#"printer connection closed");
[thePrinterConn close];
[thePrinterConn release];
});
}

Related

Zebra imz320 zpl bluetooth printer "ZSDK_API_ERROR_DOMAIN" - code: 1 error

I am trying to make an extension on my iOS app with Zebra iMZ320 printer. I followed developers guide of zebra. However ı got an error about SDK.
Here is my code:
- (IBAction)buttonPressed:(id)sender {
NSString *serialNumber = #"";
//Find the Zebra Bluetooth Accessory
EAAccessoryManager *sam = [EAAccessoryManager sharedAccessoryManager];
NSArray * connectedAccessories = [sam connectedAccessories];
for (EAAccessory *accessory in connectedAccessories) {
if([accessory.protocolStrings indexOfObject:#"com.zebra.rawport"] != NSNotFound){
serialNumber = accessory.serialNumber;
break;
//Note: This will find the first printer connected! If you have multiple Zebra printers connected, you should display a list to the user and have him select the one they wish to use
}
}
// Instantiate connection to Zebra Bluetooth accessory
id<ZebraPrinterConnection, NSObject> thePrinterConn = [[MfiBtPrinterConnection alloc] initWithSerialNumber:serialNumber];
// Open the connection - physical connection is established here.
BOOL success = [thePrinterConn open];
// This example prints "This is a ZPL test." near the top of the label.
NSString *zplData = #"^XA^FO20,20^A0N,25,25^FDThis is a ZPL test.^FS^XZ";
NSError *error = nil;
// Send the data to printer as a byte array.
success = success && [thePrinterConn write:[zplData dataUsingEncoding:NSUTF8StringEncoding] error:&error];
if (success != YES || error != nil) {
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[errorAlert show];
}
}
However when I try to print I got an error from last NSError variable. It is in write function.
domain: "ZSDK_API_ERROR_DOMAIN" - code: 1
Can someone help? Thanks in advance.
I've had the same problem, and my solution was to make a function with the code provided by ZEBRA and call this function in a different thread on the button that the user presses.
-(IBAction)buttonPressed:(id)sender {
[NSThread detachNewThreadSelector:#selector(sendZplByBluetooth) toTarget:self withObject:nil];
}
-(void)sendZplByBluetooth{
//Zebra Code Here
}
It was very expensive to realize that, I hope it helps you.
Try using zebra-toolkit. It will save you a lot of trouble with managing the printer and using ZPL.

CKAccountStatus returning different values when not called from ViewController

I'm using Cloud Kit in my app which makes use of the camera and only allows the user to submit photos if they're logged into iCloud. So when the user clicks the camera button, I call a CloudKit method to get the user's icloud status, which returns a CKAccountStatus value (0-3). I implemented this initially in the view controller and it worked perfectly. Then I did some refactoring and created a CKManager class to house all CK related methods. So now when the camera is clicked instead of calling the CK method off the container directly in the VC, I'm calling it via a method from my CKManager property (which is lazy instantiated). It should only return values 0-3, but it keeps returning 448 for some reason. However, in the CKManager logging, I can see it logging correctly that I'm logged into iCloud. So there's an issue of it translating from there back to the VC. I have feeling this is a threading/callback issue, which I'm not that well versed in.
Can someone take a look at the code and see if there's something obvious I'm doing wrong? Thanks in advance!
- (IBAction)cameraBarButtonPressed:(UIBarButtonItem *)sender {
NSLog(#"Entered cameraBarButtonPressed");
//CKContainer *container = [CKContainer defaultContainer];
dispatch_queue_t fetchQ = dispatch_queue_create("check user status", NULL);
__block CKAccountStatus userAccountStatus;
dispatch_async(fetchQ, ^{ // check user's CK status on different thread
userAccountStatus = [self.ckManager getUsersCKStatus];
NSLog(#"cameraBarButtonPressed userAccountStatus: %ld", userAccountStatus);
if (userAccountStatus == CKAccountStatusAvailable) {
//NSLog(#"User is logged into CK - user can upload pics!");
UIImagePickerController *cameraUI = [[UIImagePickerController alloc] init];
cameraUI.delegate = self; // set the deleage for the ImagePickerController
// check to see if the camera is available as source type, else check for photo album
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera;
} else if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum]) {
cameraUI.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
}
[cameraUI setAllowsEditing:YES]; // let the user edit the photo
// set the camera presentation style
//cameraUI.modalPresentationStyle = UIModalPresentationFullScreen;
cameraUI.modalPresentationStyle = UIModalPresentationCurrentContext;
dispatch_async(dispatch_get_main_queue(), ^{ // show the camera on main thread to avoid latency
[self presentViewController:cameraUI animated:YES completion:nil]; // show the camera with animation
});
} else if (userAccountStatus == CKAccountStatusNoAccount) {
//NSLog(#"User is not logged into CK - Camera not available!");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud Not Available" message:#"You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
dispatch_async(dispatch_get_main_queue(), ^{
[alert show];
});
} else if (userAccountStatus == CKAccountStatusRestricted) {
NSLog(#"User CK account is RESTRICTED !");
} else if (userAccountStatus == CKAccountStatusCouldNotDetermine) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud Status Undetermined" message:#"We could not determine your iCloud status. You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
dispatch_async(dispatch_get_main_queue(), ^{
[alert show];
});
} else { // did not get back one of the above values so show the Could Not Determine message
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud Status Undetermined" message:#"We could not determine your iCloud status. You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
dispatch_async(dispatch_get_main_queue(), ^{
[alert show];
});
}
});
}
The above code is the code that does not work. Here is the code that does work. Just copying the beginning code as the rest is redundant from that point on...
CKContainer *container = [CKContainer defaultContainer];
dispatch_async(fetchQ, ^{ // check user's CK status on different thread
[container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError *error) {
if (error) {...
Lastly, here is the code that gets called from CKManager for the code that does not work...
- (CKAccountStatus)getUsersCKStatus {
NSLog(#"Entered getUsersCKStatus...");
__block CKAccountStatus userAccountStatus;
[self.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError *error) {
if (error) {
NSLog(#"Error: Error encountered while getting user CloudKit status: %#", error.localizedDescription);
} else {
if (accountStatus == CKAccountStatusAvailable) {
NSLog(#"Info: User is logged into CK - camera is available!");
userAccountStatus = CKAccountStatusAvailable;
} else if (accountStatus == CKAccountStatusNoAccount) {
NSLog(#"Info: User is not logged into CK - Camera not available!");
userAccountStatus = CKAccountStatusNoAccount;
} else if (accountStatus == CKAccountStatusRestricted) {
NSLog(#"Info: User CK account is RESTRICTED - what does that mean!?");
userAccountStatus = CKAccountStatusRestricted;
} else if (accountStatus == CKAccountStatusCouldNotDetermine) {
NSLog(#"Error: Could not determine user CK Account Status: %#", error.localizedDescription);
userAccountStatus = CKAccountStatusCouldNotDetermine;
}
}
}];
NSLog(#"CKAccountStatus: %ld", userAccountStatus);
return userAccountStatus;
}
In the getUsersCKStatus you are calling the accountStatusWithCompletionHandler. That is an asynchronous method. In your case it will return the userAccountStatus before it is set by its callback method.
You could solve this by making that method synchronous by implementing a semaphore. A better way would be passing on a callback block to that method and not returning a value.

invitePeer timeout in Multipeer Connectivity iOS 7

I am using this method to ask a nearby device to join the session:
When I do it I also start spinning an indicator
[browser invitePeer:key
toSession:session
withContext:nil
timeout:30];
Is there a method called in the moment of timeout? what if the other device goes out of range?
EDIT:
I notice that this state is never called:
if (state == MCSessionStateConnecting) {
NSLog(#"CONNECTING %#", peerID);
}
in case of timeouts on the browser side, you need to watch for the MCSessionStateNotConnected state. i do something like this:
- (void)session:(MCSession *)session
peer:(MCPeerID *)peerID
didChangeState:(MCSessionState)state
{
if (state == MCSessionStateNotConnected)
{
if (self.isWaitingForInvitation)
{
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:NSLocalizedString(#"ERROR_TITLE", nil)
message:NSLocalizedString(#"ERROR_TEXT", nil)
delegate:self
cancelButtonTitle:NSLocalizedString(#"NO", #"Não")
otherButtonTitles:NSLocalizedString(#"YES", #"Sim"),
nil];
dispatch_sync(dispatch_get_main_queue(), ^{
[alertView show];
});
self.isWaitingForInvitation = NO;
}
}
use the dispatch_sync to make the alert popup right away.
Using a timer with a timer interval matching timeout parameter could be better idea.

Start something asynchronously, then wait on it later only if needed

I want to start a task that runs on another thread "just in case it is needed" to minimize the time the user will have to wait on it later. If there is time for it to complete, the user will not have to wait, but if it has not completed then waiting would be necessary.
Something like, opening a database in viewDidLoad: that will be needed when and if the user pushes a button on the screen. If I wait to open the database until the user actually pushes the button there is a lag. So I want to open it early. Since I don't know how long it will take to open and I don't know how long until the user hits the button, I need a way of saying, if that other task has not completed yet then wait, otherwise just go ahead.
For example:
#implementation aViewController
- (void) viewDidLoad {
[self.dbManager openOrCreateDbWithCompletionHandler: ^(NSError *err) {
if( err ) NSLog( #"There was a problem opening the database" );
}];
}
- (IBAction) goButtonTouched: (id) sender {
// Wait here until the database is open and ready to use.
if( ???DatabaseNotAvailableYet??? ) {
[self putSpinnerOnScreen];
???BlockProgressHereUntilDatabaseAvailable???
[self takeSpinnerOffScreen];
}
// Use the database...
NSManagedObjectContext *context = [self theDatabaseContext];
// Build the search request for the attribute desired
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: NSStringFromClass([Destinations class])];
request.predicate = [NSPredicate predicateWithFormat: #"dId == %#", sender.tag];
request.sortDescriptors = nil;
// Perform the search
NSError *error = nil;
NSArray *matches = [context executeFetchRequest: request error: &error];
// Use the search results
if( !matches || matches.count < 1 ) {
NSLog( #"Uh oh, got a nil back from my Destination fetch request!" );
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"No Info"
message: #"The database did not have information for this selection"
delegate: nil
cancelButtonTitle: #"OK"
otherButtonTitles: nil];
[alert show];
} else {
MyOtherViewController *movc = [[MyOtherViewContoller alloc] init];
movc.destDetails = [matches lastObject];
[self.navigationController pushViewController: movc animated: YES];
}
}
#end
My hope is that there is never a spinner on the screen and never any delay for the user but, since I don't know how long it will take for the database connection to be established, I have to be prepared for it not being ready when the user pushes the button.
I can't use the call back for when openOrCreateDbWithCompletionHandler: completes since I don't want to do anything then, only when the user pushes the button.
I thought about using a semaphore but it seems like I would only signal it once (in the completion handler of the openOrCreateDbWithCompletionHandler: call) but would wait on it every time a button was pushed. That seems like it would only work for the first button push.
I thought about using dispatch_group_async() for openOrCreateDbWithCompletionHandler: then dispatch_group_wait() in goButtonTouched: but since openOrCreateDbWithCompletionHandler: does its work on another thread and returns immediately, I don't think the wait state would be set.
I can simply set a my own flag, something like before the openOrCreateDbWithCompletionHandler:, self.notOpenYet = YES;, then in its completion handler do self.notOpenYet = NO;, then in goButtonTouched: replace ???DatabaseNotAvailableYet??? with self.notOpenYet, but then how do I block progress on its state? Putting in loops and timers seems kludgy since I don't know if the wait will be nanoseconds or seconds.
This seems like a common enough situation, I am sure that you have all done this sort of thing commonly and it is poor education on my side, but I have searched stackOverflow and the web and have not found a satisfying answer.
I think, blocking execution is a bad habit unless you are building your own event loop, which is rarely necessary. You also don't need to do any GCD stuff in your case. Just get a feeling for async.
The following should work:
#implementation aViewController
- (void) viewDidLoad {
self.waitingForDB = NO;
self.databaseReady = NO;
[self.dbManager openOrCreateDbWithCompletionHandler: ^(NSError *err) {
if( err ){
NSLog( #"There was a problem opening the database" )
}else{
[self performSelectorOnMainThread:#selector(handleDatabaseReady) withObject:nil waitUntilDone:NO];
};
}];
}
- (void)handleDatabaseReady{
self.databaseReady = YES;
if(self.waitingForDB){
[self takeSpinnerOffScreen];
[self go];
}
}
- (IBAction) goButtonTouched: (id) sender {
// Wait here until the database is open and ready to use.
if( !self.databaseReady ) {
self.waitingForDB = YES;
[self putSpinnerOnScreen];
else{
[self go];
}
}
-(void)go{
// Use the database...
NSManagedObjectContext *context = [self theDatabaseContext];
// Build the search request for the attribute desired
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: NSStringFromClass([Destinations class])];
request.predicate = [NSPredicate predicateWithFormat: #"dId == %#", sender.tag];
request.sortDescriptors = nil;
// Perform the search
NSError *error = nil;
NSArray *matches = [context executeFetchRequest: request error: &error];
// Use the search results
if( !matches || matches.count < 1 ) {
NSLog( #"Uh oh, got a nil back from my Destination fetch request!" );
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"No Info"
message: #"The database did not have information for this selection"
delegate: nil
cancelButtonTitle: #"OK"
otherButtonTitles: nil];
[alert show];
} else {
MyOtherViewController *movc = [[MyOtherViewContoller alloc] init];
movc.destDetails = [matches lastObject];
[self.navigationController pushViewController: movc animated: YES];
}
}
#end
Performing the call to handleDatabaseReady on the main thread guarantees that no race conditions in setting/reading your new properties will appear.
I'd go with the flag. You don't want to block the UI, just show the spinner and return from the goButtonTouched. However, you do need to cancel the spinner, if it is active, in openOrCreateDbWithCompletionHandler:.
This is rather a simple scenario. You make a method that does the stuff. Lets call it doStuff. From main thread, you call performSelectorInBackgroundThread:#selector(doStuff). Do not enable the button by default. Enable it at the end of doStuff so that user won't tap on it until you are ready. To make it more appealing, you can place a spinner in the place of the button and then replace it with the button when doStuff completes.
There are a number of classes and APIs you can use to achieve this kind of thing. You can use NSThread with synchronization primitives like semaphores and events to wait for it to finish when the user actually presses the button. You can use an NSOperation subclass (with an NSOperationQueue), and you can use GCD queues.
I would suggest you take a look at some the information in the Concurrency Programming Guide from Apple.
In your case you would probably be best served adding the operation to a GCD background queue using dispatch_async in combination with a semaphore which you can wait on when the user taps the button. You can check out the question "How do I wait for an asynchronously dispatched block to finish?" for an example.

Display a loading indicator after user taps "Share" and before request is completed?

The issue I have is that after users tap on the "Share" button of the FBDialog, they do not get any visual feedback until the request completes or fail... and over 3G that can takes a while. During this time users do not know if they tapped "Share" correctly or not, and there's risk of posting content twice.
Is there a way I can get a callback so I can display a loading indicator during this time? Where should I put this piece in?
Thank you!
Below code you write to fbAgent.m and also obser code here u get one "PID" at that time u write to alert msg ....... user can understand to that msg was success posted Facebook.
I hope this helps.
- (void)request:(FBRequest*)request didLoad:(id)result {
if ([request.method isEqualToString:#"facebook.fql.query"]) {
NSArray* users = result;
NSDictionary* user = [users objectAtIndex:0];
NSString* name = [user objectForKey:#"name"];
// Calling the delegate callback
[delegate facebookAgent:self didLoadName:name];
} else if ([request.method isEqualToString:#"facebook.users.setStatus"]) {
newStatus = nil;
NSString* success = result;
if ([success isEqualToString:#"1"]) {
// Calling the delegate callback
[delegate facebookAgent:self statusChanged:YES];
} else {
[delegate facebookAgent:self statusChanged:NO];
}
} else if ([request.method isEqualToString:#"facebook.photos.upload"]) {
NSDictionary* photoInfo = result;
NSString* pid = [photoInfo objectForKey:#"pid"];
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:nil message:[NSString stringWithFormat:#"success Uploaded Your image please check your FaceBook", pid] delegate:self cancelButtonTitle:nil otherButtonTitles:#"Ok", nil];
[alertView show];
[alertView release];
fbbut.enabled=YES;
[fbact stopAnimating];
self.uploadImageData = nil;
self.uploadImageCaption = nil;
self.uploadImageAlbum = nil;
//[delegate facebookAgent:self photoUploaded:pid];
if(newStatus){
[self setStatus:newStatus];
}
}
}
Start your indicator when you click on button and stop on Delegate of Facebook function
- (void)dialogDidComplete:(FBDialog *)dialog
Hope it helps....

Resources