iOS Bluetooth 打印小票(二)

在上一篇中介紹了打印小票所須要的命令,這一篇介紹Bluetooth鏈接藍牙和打印小票的全過程。git

小票

CoreBluetooth的封裝

由於CoreBluetooth中的代理太多,而每一次操做又比較依賴上一次操做的結果,方法又比較零散,因此我作了粗略封裝,把代理改爲了block方式回調。github

1.獲取藍牙管理單例

HLBLEManager *manager = [HLBLEManager sharedInstance];
    __weak HLBLEManager *weakManager = manager;
    manager.stateUpdateBlock = ^(CBCentralManager *central) {
        NSString *info = nil;
        switch (central.state) {
            case CBCentralManagerStatePoweredOn:
                info = @"藍牙已打開,而且可用";
                //三種種方式
                // 方式1
                [weakManager scanForPeripheralsWithServiceUUIDs:nil options:nil];
                // 方式2
                [central scanForPeripheralsWithServices:nil options:nil];
                // 方式3
                [weakManager scanForPeripheralsWithServiceUUIDs:nil options:nil didDiscoverPeripheral:^(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI) {
                    
                }];
                break;
            case CBCentralManagerStatePoweredOff:
                info = @"藍牙可用,未打開";
                break;
            case CBCentralManagerStateUnsupported:
                info = @"SDK不支持";
                break;
            case CBCentralManagerStateUnauthorized:
                info = @"程序未受權";
                break;
            case CBCentralManagerStateResetting:
                info = @"CBCentralManagerStateResetting";
                break;
            case CBCentralManagerStateUnknown:
                info = @"CBCentralManagerStateUnknown";
                break;
        }
        
        [SVProgressHUD setDefaultStyle:SVProgressHUDStyleDark];
        [SVProgressHUD showInfoWithStatus:info ];
    };
複製代碼

由於CBCentralManager一建立,就會在代理中返回藍牙模塊的狀態,因此及時設置狀態返回的回調,以便在搜索附近可用的藍牙外設。web

2.搜索可用的藍牙外設

                // 方式1
                [weakManager scanForPeripheralsWithServiceUUIDs:nil options:nil];
                // 方式2
                [central scanForPeripheralsWithServices:nil options:nil];
                // 方式3
                [weakManager scanForPeripheralsWithServiceUUIDs:nil options:nil didDiscoverPeripheral:^(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI) {
                    
                }];
複製代碼

這裏給出了三種方式,前兩種方式都須要先設置好搜索到藍牙外設以後的回調, 即:bash

    manager.discoverPeripheralBlcok = ^(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI) {
        if (peripheral.name.length <= 0) {
            return ;
        }
        
        if (self.deviceArray.count == 0) {
            NSDictionary *dict = @{@"peripheral":peripheral, @"RSSI":RSSI};
            [self.deviceArray addObject:dict];
        } else {
            BOOL isExist = NO;
            for (int i = 0; i < self.deviceArray.count; i++) {
                NSDictionary *dict = [self.deviceArray objectAtIndex:i];
                CBPeripheral *per = dict[@"peripheral"];
                if ([per.identifier.UUIDString isEqualToString:peripheral.identifier.UUIDString]) {
                    isExist = YES;
                    NSDictionary *dict = @{@"peripheral":peripheral, @"RSSI":RSSI};
                    [_deviceArray replaceObjectAtIndex:i withObject:dict];
                }
            }
            
            if (!isExist) {
                NSDictionary *dict = @{@"peripheral":peripheral, @"RSSI":RSSI};
                [self.deviceArray addObject:dict];
            }
        }
        
        [self.tableView reloadData];
        
    };
}
複製代碼

第三種方式,則附帶一個block,便於直接處理。網絡

3.鏈接藍牙外設

HLBLEManager *manager = [HLBLEManager sharedInstance];
    [manager connectPeripheral:_perpheral
                connectOptions:@{CBConnectPeripheralOptionNotifyOnDisconnectionKey:@(YES)}
        stopScanAfterConnected:YES
               servicesOptions:nil
        characteristicsOptions:nil
                 completeBlock:^(HLOptionStage stage, CBPeripheral *peripheral, CBService *service, CBCharacteristic *character, NSError *error) {
                     switch (stage) {
                         case HLOptionStageConnection:
                         {
                             if (error) {
                                 [SVProgressHUD showErrorWithStatus:@"鏈接失敗"];
                                 
                             } else {
                                 [SVProgressHUD showSuccessWithStatus:@"鏈接成功"];
                             }
                             break;
                         }
                         case HLOptionStageSeekServices:
                         {
                             if (error) {
                                 [SVProgressHUD showSuccessWithStatus:@"查找服務失敗"];
                             } else {
                                 [SVProgressHUD showSuccessWithStatus:@"查找服務成功"];
                                 [_infos addObjectsFromArray:peripheral.services];
                                 [_tableView reloadData];
                             }
                             break;
                         }
                         case HLOptionStageSeekCharacteristics:
                         {
                             // 該block會返回屢次,每個服務返回一次
                             if (error) {
                                 NSLog(@"查找特性失敗");
                             } else {
                                 NSLog(@"查找特性成功");
                                 [_tableView reloadData];
                             }
                             break;
                         }
                         case HLOptionStageSeekdescriptors:
                         {
                             // 該block會返回屢次,每個特性返回一次
                             if (error) {
                                 NSLog(@"查找特性的描述失敗");
                             } else {
                                 NSLog(@"查找特性的描述成功");
                             }
                             break;
                         }
                         default:
                             break;
                     }
                     
                 }];
複製代碼

由於鏈接藍牙外設--->掃描藍牙外設服務--->掃描藍牙外設服務特性--->掃描特性描述app

這些操做都是有階段性的,而且依賴上一步的結果。ide

這裏我也給出了兩種方式:佈局

方式一(推薦):如上面代碼同樣,設置最後一個參數block,而後在block中判斷當前是哪一個階段的回調。測試

方式二:提早設置好每一階段的block,而後設置方法中最後一個參數的block爲nil。字體

/** 鏈接外設完成的回調 */
@property (copy, nonatomic) HLConnectCompletionBlock                connectCompleteBlock;
/** 發現服務的回調 */
@property (copy, nonatomic) HLDiscoveredServicesBlock               discoverServicesBlock;
/** 發現服務中的特性的回調 */
@property (copy, nonatomic) HLDiscoverCharacteristicsBlock          discoverCharacteristicsBlock;
/** 發現特性的描述的回調 */
@property (copy, nonatomic) HLDiscoverDescriptorsBlock              discoverDescriptorsBlock;
複製代碼

4.記錄下藍牙外設中的可寫特性

記錄下特性中的可寫服務以便,往這個藍牙外設中寫入數據。

CBCharacteristic *character = [service.characteristics objectAtIndex:indexPath.row];
    CBCharacteristicProperties properties = character.properties;
    if (properties & CBCharacteristicPropertyWriteWithoutResponse) {
        self.chatacter = character;
    }
複製代碼

5.拼裝要寫入到藍牙的數據

        NSString *title = @"測試電商";
        NSString *str1 = @"測試電商服務中心(銷售單)";
        
        HLPrinter *printer = [[HLPrinter alloc] init];
        [printer appendText:title alignment:HLTextAlignmentCenter fontSize:HLFontSizeTitleBig];
        [printer appendText:str1 alignment:HLTextAlignmentCenter];
        [printer appendBarCodeWithInfo:@"RN3456789012"];
        [printer appendSeperatorLine];
        
        [printer appendTitle:@"時間:" value:@"2016-04-27 10:01:50" valueOffset:150];
        [printer appendTitle:@"訂單:" value:@"4000020160427100150" valueOffset:150];
        [printer appendText:@"地址:深圳市南山區學府路東深大店" alignment:HLTextAlignmentLeft];
        
        [printer appendSeperatorLine];
        [printer appendLeftText:@"商品" middleText:@"數量" rightText:@"單價" isTitle:YES];
        CGFloat total = 0.0;
        for (NSDictionary *dict in goodsArray) {
            [printer appendLeftText:dict[@"name"] middleText:dict[@"amount"] rightText:dict[@"price"] isTitle:NO];
            total += [dict[@"price"] floatValue] * [dict[@"amount"] intValue];
        }
        
        [printer appendSeperatorLine];
        NSString *totalStr = [NSString stringWithFormat:@"%.2f",total];
        [printer appendTitle:@"總計:" value:totalStr];
        [printer appendTitle:@"實收:" value:@"100.00"];
        NSString *leftStr = [NSString stringWithFormat:@"%.2f",100.00 - total];
        [printer appendTitle:@"找零:" value:leftStr];
        
        [printer appendFooter:nil];
        
        [printer appendImage:[UIImage imageNamed:@"ico180"] alignment:HLTextAlignmentCenter maxWidth:300];
        
        NSData *mainData = [printer getFinalData];
複製代碼

6.寫入數據

HLBLEManager *bleManager = [HLBLEManager sharedInstance];
        [bleManager writeValue:mainData forCharacteristic:self.chatacter type:CBCharacteristicWriteWithoutResponse];
複製代碼

寫入數據後,藍牙打印機就會開始打印小票。

藍牙打印機操做封裝

1.建立一個打印操做對象

HLPrinter *printer = [[HLPrinter alloc] init];
複製代碼

在建立這個打印機操做對象時,內部作了不少預設置:

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self defaultSetting];
    }
    return self;
}

- (void)defaultSetting
{
    _printerData = [[NSMutableData alloc] init];
    
    // 1.初始化打印機
    Byte initBytes[] = {0x1B,0x40};
    [_printerData appendBytes:initBytes length:sizeof(initBytes)];
    // 2.設置行間距爲1/6英寸,約34個點
    // 另外一種設置行間距的方法看這個 @link{-setLineSpace:}
    Byte lineSpace[] = {0x1B,0x32};
    [_printerData appendBytes:lineSpace length:sizeof(lineSpace)];
    // 3.設置字體:標準0x00,壓縮0x01;
    Byte fontBytes[] = {0x1B,0x4D,0x00};
    [_printerData appendBytes:fontBytes length:sizeof(fontBytes)];
}
複製代碼

2.設置要打印的內容

能夠打印的內容包括:文字、二維碼、條形碼、圖片。 而對這些內容的處理已經作了封裝,只須要簡單調用某些API便可。

2.1 打印單行文字

/**
 *  添加單行標題,默認字號是小號字體
 *
 *  @param title     標題名稱
 *  @param alignment 標題對齊方式
 */
- (void)appendText:(NSString *)text alignment:(HLTextAlignment)alignment;

/**
 *  添加單行標題
 *
 *  @param title     標題名稱
 *  @param alignment 標題對齊方式
 *  @param fontSize  標題字號
 */
- (void)appendText:(NSString *)text alignment:(HLTextAlignment)alignment fontSize:(HLFontSize)fontSize;
複製代碼

2.2 打印左標題,右內容文字

/**
 *  添加單行信息,左邊名稱(左對齊),右邊實際值(右對齊)。
 *  @param title    名稱
 *  @param value    實際值
 *  @param fontSize 字號大小
 *  警告:因字號和字體與iOS中字體不一致,計算出來有偏差
 */
- (void)appendTitle:(NSString *)title value:(NSString *)value fontSize:(HLFontSize)fontSize;

/**
 *  設置單行信息,左標題,右實際值
 *
 *  @param title    標題
 *  @param value    實際值
 *  @param offset   實際值偏移量
 */
- (void)appendTitle:(NSString *)title value:(NSString *)value valueOffset:(NSInteger)offset;

/**
 *  設置單行信息,左標題,右實際值
 *
 *  @param title    標題
 *  @param value    實際值
 *  @param offset   實際值偏移量
 *  @param fontSize 字號
 */
- (void)appendTitle:(NSString *)title value:(NSString *)value valueOffset:(NSInteger)offset fontSize:(HLFontSize)fontSize;
複製代碼

3.三列數據樣式

/**
 *  添加選購商品信息標題,通常是三列,名稱、數量、單價
 *
 *  @param LeftText   左標題
 *  @param middleText 中間標題
 *  @param rightText  右標題
 */
- (void)appendLeftText:(NSString *)left middleText:(NSString *)middle rightText:(NSString *)right isTitle:(BOOL)isTitle;
複製代碼

4.打印條形碼

/**
 *  添加條形碼圖片
 *
 *  @param info 條形碼中包含的信息,默認居中顯示,最大寬度爲300。若是大於300,會等比縮放。
 */
- (void)appendBarCodeWithInfo:(NSString *)info;

/**
 *  添加條形碼圖片
 *
 *  @param info      條形碼中的信息
 *  @param alignment 圖片對齊方式
 *  @param maxWidth  圖片最大寬度
 */
- (void)appendBarCodeWithInfo:(NSString *)info alignment:(HLTextAlignment)alignment maxWidth:(CGFloat)maxWidth;
複製代碼

5.打印二維碼

/**
 *  添加二維碼圖片
 *
 *  @param info 二維碼中的信息
 */
- (void)appendQRCodeWithInfo:(NSString *)info;

/**
 *  添加二維碼圖片
 *
 *  @param info        二維碼中的信息
 *  @param centerImage 二維碼中間的圖片
 *  @param alignment   對齊方式
 *  @param maxWidth    二維碼的最大寬度
 */
- (void)appendQRCodeWithInfo:(NSString *)info centerImage:(UIImage *)centerImage alignment:(HLTextAlignment)alignment maxWidth:(CGFloat )maxWidth;
複製代碼

6.打印圖片

/**
 *  添加圖片,通常是添加二維碼或者條形碼
 *
 *  @param image     圖片
 *  @param alignment 圖片對齊方式
 *  @param maxWidth  圖片的最大寬度,若是圖片過大,會等比縮放
 */
- (void)appendImage:(UIImage *)image alignment:(HLTextAlignment)alignment maxWidth:(CGFloat)maxWidth;
複製代碼

7.打印分隔線

/**
 *  添加一條分割線,like this:---------------------------
 */
- (void)appendSeperatorLine;
複製代碼

8.打印footer

/**
 *  添加底部信息
 *
 *  @param footerInfo 不填默認爲 謝謝惠顧,歡迎下次光臨!
 */
- (void)appendFooter:(NSString *)footerInfo;
複製代碼

9.獲取最終數據

/**
 *  獲取最終的data
 *
 *  @return 最終的data
 */
- (NSData *)getFinalData;
複製代碼

各類樣式展現
而HLPrinter內部實際有一些私有方法,都是對上一篇內容中打印機命令的封裝,做爲基礎操做 例如:

/**
 *  換行
 */
- (void)appendNewLine
{
    Byte nextRowBytes[] = {0x0A};
    [_printerData appendBytes:nextRowBytes length:sizeof(nextRowBytes)];
}

/**
 *  回車
 */
- (void)appendReturn
{
    Byte returnBytes[] = {0x0D};
    [_printerData appendBytes:returnBytes length:sizeof(returnBytes)];
}

/**
 *  設置對齊方式
 *
 *  @param alignment 對齊方式:居左、居中、居右
 */
- (void)setAlignment:(HLTextAlignment)alignment
{
    Byte alignBytes[] = {0x1B,0x61,alignment};
    [_printerData appendBytes:alignBytes length:sizeof(alignBytes)];
}

/**
 *  設置字體大小
 *
 *  @param fontSize 字號
 */
- (void)setFontSize:(HLFontSize)fontSize
{
    Byte fontSizeBytes[] = {0x1D,0x21,fontSize};
    [_printerData appendBytes:fontSizeBytes length:sizeof(fontSizeBytes)];
}
複製代碼

UIImage+Bitmap中,主要是對圖片操做的兩個Category,一個是建立二維碼、條形碼圖片。

另外一個是將圖片轉換爲點陣圖數據。

補充

可能對於小票的樣式不只僅侷限於封裝的幾種,有人提到左邊二維碼圖片,右邊居中顯示一些文字的佈局方式,這樣用原來的指令集組合的方式就很難實現。

對於一些不太好弄的佈局樣式,咱們能夠曲線救國,這裏有一些新的場景和解決方案:

  1. 能夠先在容器視圖上實現,而後再截取容器視圖,將截取後的圖片打印出來就能夠啦😃 。
  2. 用HTML作出訂單佈局,而後用UIWebView加載出來後,截取WebView完整內容,再打印出來。

用UIWebView打印的方式,還能夠在線修改訂單的樣式和佈局,就是比較浪費墨,沒有指令集組合的方式打印出來的清晰。 如下是利用UIWebView,而後獲取WebView快照打印出來的小票:

小票

獲取UIWebView的完整內容截圖的方法:

/**
 *  獲取當前加載的網頁的截圖
 *  獲取當前WebView的size,而後一屏一屏的截圖後,再拼接成一張完整的圖片
 *
 *  @return
 */
- (UIImage *)imageForWebView
{
    // 1.獲取WebView的寬高
    CGSize boundsSize = self.bounds.size;
    CGFloat boundsWidth = boundsSize.width;
    CGFloat boundsHeight = boundsSize.height;
    
    // 2.獲取contentSize
    CGSize contentSize = self.scrollView.contentSize;
    CGFloat contentHeight = contentSize.height;
    // 3.保存原始偏移量,便於截圖後復位
    CGPoint offset = self.scrollView.contentOffset;
    // 4.設置最初的偏移量爲(0,0);
    [self.scrollView setContentOffset:CGPointMake(0, 0)];
    
    NSMutableArray *images = [NSMutableArray array];
    while (contentHeight > 0) {
        // 5.獲取CGContext 5.獲取CGContext
        UIGraphicsBeginImageContextWithOptions(boundsSize, NO, 0.0);
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        // 6.渲染要截取的區域
        [self.layer renderInContext:ctx];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        // 7.截取的圖片保存起來
        [images addObject:image];
        
        CGFloat offsetY = self.scrollView.contentOffset.y;
        [self.scrollView setContentOffset:CGPointMake(0, offsetY + boundsHeight)];
        contentHeight -= boundsHeight;
    }
    // 8 webView 恢復到以前的顯示區域
    [self.scrollView setContentOffset:offset];
    
    CGFloat scale = [UIScreen mainScreen].scale;
    
    CGSize imageSize = CGSizeMake(contentSize.width * scale,
                                  contentSize.height * scale);
    // 9.根據設備的分辨率從新繪製、拼接成完整清晰圖片
    UIGraphicsBeginImageContext(imageSize);
    [images enumerateObjectsUsingBlock:^(UIImage *image, NSUInteger idx, BOOL *stop) {
        [image drawInRect:CGRectMake(0,
                                     scale * boundsHeight * idx,
                                     scale * boundsWidth,
                                     scale * boundsHeight)];
    }];
    
    UIImage *fullImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return fullImage;
}
複製代碼

想要體驗這種方式的能夠在BLEDetailViewControllerviewDidLoad方法中,將導航欄右按鈕的註釋修改下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.title = @"藍牙詳情";
    
//    UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithTitle:@"商品" style:UIBarButtonItemStylePlain target:self action:@selector(goToShopping)];
    
    UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithTitle:@"網絡訂單" style:UIBarButtonItemStylePlain target:self action:@selector(goToOrder)];
                                  
    self.navigationItem.rightBarButtonItem = rightItem;
    
    _infos = [[NSMutableArray alloc] init];
    _tableView.rowHeight = 60;
    
    //鏈接藍牙並展現詳情
    [self loadBLEInfo];
}
複製代碼

補充一些參數: 據佳博的一技術人員提供的一些參數:

  • 漢字是24 x 24點陣,字符是12 x 24。
  • 58mm 型打印機橫向寬度384個點。(但是我用文字設置相對位置測試確實368,囧)
  • 80mm 型打印機橫向寬度576個點。
  • 1mm 大概是8個點。

完整的庫和Demo地址:github地址

若是你只關注iOS 打印小票部分,不想太多操做藍牙鏈接和處理,看這裏:藍牙打印小票

打印沒反應?

首先,肯定你使用的是標籤打印機仍是通常的小票打印機。

我寫的Demo不支持標籤打印機,你能夠仿照個人例子,本身封裝一下指令(咱們並無採購標籤打印機,也沒辦法測試,抱歉了)。

若是你鏈接成功,可是發出打印指令後,打印機沒反應,頗有多是由於你的打印機一次發送的數據長度小於146,你把146改的更小一點試試看。

我測試的兩臺佳博打印機,一臺沒有長度限制,一臺最多每次只能發送146個字節,不然會出現打印沒反應的狀況,須要重啓打印機。

不一樣的打印機,可能對長度的限制不太同樣,據羣友反應有的打印機只能支持一次發送20個字節,因此你須要將宏裏面的146改的更小一點。

相關文章
相關標籤/搜索