实战分享:Swift 蓝牙打印机数据排版

2021-09-29 15:10:24 浏览数 (1)

前言

蓝牙打印机打印排版 本次使用的是 Swift 5 构建,蓝牙连接打印机打印

功能包含:

  • 两列排版
  • 两列左右侧标题自动换行
  • 三列排版
  • 四列排版
  • 四列排版自动换行
  • 根据打印纸的大小(50mm、80mm)自动排版
  • 对齐方式(两列左对齐、有对齐)
  • 单列左对齐、居中对齐、右对齐
  • 字体大小设置

效果图

备注两列自动换行、四列商品自动换行

使用方法

BaseManager.swift 文件导入项目 (文件内容在下面)

在需要使用的 VC

代码语言:javascript复制
// 变量生命
var manager:BaseManager?


// 初始化
if manager == nil{
    manager = BaseManager()
    manager?.delegate = self
    manager?.successBlock = {
        self.printerBtn.isEnabled = true
        print("连接成功")
        self.tableView.reloadData()
    }
}

// 接收搜索到打印机的回调
extension ViewController: BaseManagerDelegate {
    func discoverPeripheral(_ peripheral: CBPeripheral) {
        peripheralArr.append(peripheral)
        tableView.reloadData()
    }
}

// 打印测试数据
@objc func printTextAction() {
    manager?.testPrint()
}

核心代码

代码语言:javascript复制
/**
 * 打印四列 自动换行
 * max 最大4字
 * @param leftText   左侧文字
 * @param middleLeftText 中间左文字
 * @param middleRIghtText 中间右文字
 * @param rightText  右侧文字
 * @return
 */
func printFourDataAutoLine(leftText: String, middleLeftText: String, middleRIghtText: String, rightText: String) ->[Data]{
    // 存放打印的数据(data)
    var printerAllDataArr: [Data] = []
    // 每一列可显示汉子的个数
    let maxTextCount = LINE_BYTE_SIZE/4
    maxLine = 0
    let leftStrArr = printStrArrWithText(text: leftText, maxTextCount: maxTextCount)
    var middleLeftStrArr = printStrArrWithText(text: middleLeftText, maxTextCount: maxTextCount)
    var middleRightStrArr = printStrArrWithText(text: middleRIghtText, maxTextCount: maxTextCount)
    var rightStrArr = printStrArrWithText(text: rightText, maxTextCount: maxTextCount)
    for i in 0..<maxLine {
        let data = printFourData(leftText: leftStrArr[i], middleLeftText: middleLeftStrArr[i], middleRIghtText: middleRightStrArr[i], rightText: rightStrArr[i])
        printerAllDataArr.append(data)
    }
    return printerAllDataArr
}

// 字符串根据一行最大值maxTextCount分成数组
func printStrArrWithText(text: String,maxTextCount: Int) -> [String] {
    let enc = CFStringConvertEncodingToNSStringEncoding(UInt32(CFStringEncodings.GB_18030_2000.rawValue))

    var textArr:[String] = []
    let textData = setTitle(text: text) as NSData
    let textLength = textData.length
    if textLength > maxTextCount {
        // 需要几行
        let lines = textLength / maxTextCount
        // 余数
        let remainder = textLength % maxTextCount
        // 设置最大支持7行
        for i in 0..<lines {
            let temp = textData.subdata(with: NSMakeRange(i*maxTextCount, maxTextCount))
            let str = String(data: temp, encoding: String.Encoding(rawValue: enc))
            if str == nil {
                let temp = textData.subdata(with: NSMakeRange(i*(maxTextCount-1), maxTextCount))
                let str = String(data: temp, encoding: String.Encoding(rawValue: enc))
                if str != nil {
                    textArr.append(str!)
                }
            }else {
                textArr.append(str!)
            }
        }
        // 记录的值 小于当先行书 并且 有余数 就lines 1 否则 记录lines
        if maxLine < lines && remainder != 0{
            maxLine = lines   1
        }else if maxLine < lines && remainder == 0{
            maxLine = lines
        }
        if remainder != 0 {
            let temp = textData.subdata(with: NSMakeRange(lines*maxTextCount, remainder))
            let str = String(data: temp, encoding: String.Encoding(rawValue: enc))
            textArr.append(str!)
        }
    }else { // 文本没有超过限制
        if maxLine == 0 {
            maxLine = 1
        }
        textArr.append(text)
    }
    if textArr.count < 5 { // 最多支持5
        for _ in 0..<5-textArr.count {
            textArr.append("")
        }
    }
    return textArr
}

/**
 * 打印三列
 *
 * @param leftText   左侧文字
 * @param middleText 中间文字
 * @param rightText  右侧文字
 * @return
 */
func printThreeData(leftText: String, middleText: String, rightText: String) ->String{
    var strText = ""

    let leftTextLength = (setTitle(text: leftText) as NSData).length
    let middleTextLength = (setTitle(text: middleText)  as NSData).length
    let rightTextLength = (setTitle(text: rightText)  as NSData).length

    strText = strText   leftText

    // 计算左侧文字和中间文字的空格长度
    let marginBetweenLeftAndMiddle = LEFT_LENGTH - leftTextLength - middleTextLength / 2;
    for _ in 0..<marginBetweenLeftAndMiddle {
        strText = strText   " "
    }
    strText = strText   middleText

    // 计算右侧文字和中间文字的空格长度
    let marginBetweenMiddleAndRight = RIGHT_LENGTH - middleTextLength / 2 - rightTextLength;

    for _ in 0..<(marginBetweenMiddleAndRight) {
        strText = strText   " "
    }
    strText = strText   rightText
    return strText
}

// 打印两列
func printTwoData(leftText: String, rightText: String) ->Data {
    var strText = ""
    let leftTextLength = (setTitle(text: leftText) as NSData).length
    let rightTextLength = (setTitle(text: rightText)  as NSData).length
    strText = strText   leftText

    // 计算文字中间的空格
    let marginBetweenMiddleAndRight = LINE_BYTE_SIZE - leftTextLength - rightTextLength;
    for _ in 0..<marginBetweenMiddleAndRight {
        strText = strText   " "
    }
    strText = strText   rightText


    let data = NSMutableData()
    let lineData = nextLine(number: 1)
    data.append(setTitle(text: strText))
    data.append(lineData)
    return data as Data
}


//  两列 右侧文本自动换行 maxChar 个字符
func setRightTextAutoLine(left: String,right: String,maxText:Int)->Data {

    // 存放打印的数据(data)
    let printerData: NSMutableData = NSMutableData.init()

    let valueCount = right.count
    if valueCount > maxText {
        // 需要几行
        let lines = valueCount / maxText
        // 余数
        let remainder = valueCount % maxText
        for i in 0..<lines {
            let index1 = right.index(right.startIndex, offsetBy: i*maxText)
            let index2 = right.index(right.startIndex, offsetBy: i*maxText   maxText)
            let sub1 = right[index1..<index2]
            print(sub1)
            if i == 0 {
                let tempData = printTwoData(leftText: left, rightText: String(sub1))
                printerData.append(tempData)
            }else {
                let tempData = printTwoData(leftText: "", rightText: String(sub1))
                printerData.append(tempData)
            }
        }
        if remainder != 0 {
            let index1 = right.index(right.startIndex, offsetBy: lines*maxText)
            let index2 = right.index(right.startIndex, offsetBy: lines*maxText   remainder)
            let sub1 = right[index1..<index2]
            print(sub1)
            let tempData = printTwoData(leftText: "", rightText: String(sub1))
            printerData.append(tempData)
        }
    }else {
        let tempData = printTwoData(leftText: left, rightText: right)
        printerData.append(tempData)
    }

    let lineData = nextLine(number: 1)
    printerData.append(lineData)
    return printerData as Data
}


//   text 内容。value 右侧内容 左侧列 支持的最大显示 超过四字自动换行
//  两列 左侧文本自动换行
func setLeftTextLine(text: String,value: String,maxChar:Int)->Data {
    let data = text.data(using: String.Encoding(rawValue: enc))! as NSData

    if (data.length > maxChar) {
        let lines = data.length / maxChar
        let remainder = data.length % maxChar
        var tempData: NSMutableData = NSMutableData.init()
        for i in 0..<lines {
            let temp = (data.subdata(with: NSMakeRange(i*maxChar, maxChar)) as NSData)
            tempData.append(temp.bytes, length: temp.length)
            if i == 0 {
                let data = setOffsetText(value: value)
                tempData.append(data.bytes, length: data.length)
            }
            let line = nextLine(number: 1) as NSData
            tempData.append(line.bytes, length: line.length)
        }
        if remainder != 0 { // 余数不0
            let temp = data.subdata(with: NSMakeRange(lines*maxChar, remainder)) as NSData
            tempData.append(temp.bytes, length: temp.length)
        }
        return tempData as Data
    }
    let rightTextData = setOffsetText(value: value)
    let mutData = NSMutableData.init(data: data as Data)
    mutData.append(rightTextData.bytes, length: rightTextData.length)
    return mutData as Data
}

代码中使用的数字含义

代码语言:javascript复制
// 这些数字都是10进制的 ASCII码 
let ESC:UInt8   = 27    //换码
let FS:UInt8    = 28    //文本分隔符
let GS:UInt8    = 29    //组分隔符
let DLE:UInt8   = 16    //数据连接换码
let EOT:UInt8   = 4     //传输结束
let ENQ:UInt8   = 5     //询问字符
let SP:UInt8    = 32    //空格
let HT:UInt8    = 9     //横向列表
let LF:UInt8    = 10    //打印并换行(水平定位)
let ER:UInt8    = 13    //归位键
let FF:UInt8    = 12    //走纸控制(打印并回到标准模式(在页模式下) )

打印机支持的指令

如何知道打印机支持的指令

本项目中有一个 <<58MM热敏打印机编程手册>> 这里面记录了,打印机支持的所有格式,可以自行查看。


补充一下 BaseManager 这个类文件内容

代码语言:javascript复制
//
//  BaseManager.swift
//  WorldDoctor
//
//  Created by Max on 2019/7.20
//  Copyright © 2019年 xiangguohe. All rights reserved.
//

// 蓝牙打印机

import UIKit
import CoreBluetooth


protocol BaseManagerDelegate {
    // 设备发现回调 刷新页面
    func discoverPeripheral(_ peripheral: CBPeripheral)
}

class BaseManager: NSObject {
    var successBlock:(()->Void)?
    var delegate: BaseManagerDelegate?
    var command = BTPrinter()
    var manager: CBCentralManager!
    var currentPeripheral: CBPeripheral?
    var writeCharacteristic: CBCharacteristic!
    var currentServiceUUID: String?
    var currentWriteUUID: String?
    var currentScanName: String?
    let serviceId = "49535343-FE7D-4AE5-8FA9-9FAFD205E455"
    let WriteUUID = "49535343-8841-43F4-A8D4-ECBE34729BB3"
    
    override init() {
        super.init()
        self.manager = CBCentralManager(delegate: nil, queue: nil)
        self.manager.delegate = self;
        currentServiceUUID = serviceId
        currentWriteUUID = WriteUUID
        currentScanName = "Printer"
        updatePrinter()
    }

    //  刷新设备
    func updatePrinter() {
        // 发现设备 调用之后 中心管理者会为他的委托对象调用
        manager.scanForPeripherals(withServices: nil, options: nil)
    }
    //  链接设备
    func connectPrinter(peripheral: CBPeripheral) {
        if peripheral.name != currentPeripheral?.name  {
            if currentPeripheral != nil {
                manager.cancelPeripheralConnection(currentPeripheral!)
            }
        }else if peripheral.name == currentPeripheral?.name {

        }
        currentPeripheral = peripheral
        manager.connect(peripheral, options: nil)
    }
    
}
//MARK: - 蓝牙中心设备代理方法
extension BaseManager: CBCentralManagerDelegate {
    //    1.
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        
        var message = "";
        switch central.state {
        case .unknown:
            message = "蓝牙系统错误"
        case .resetting:
            message = "请重新开启手机蓝牙"
        case .unsupported:
            message = "该手机不支持蓝牙"
        case .unauthorized:
            message = "蓝牙验证失败"
        case .poweredOff://蓝牙没开启,直接到设置
            message = "蓝牙没有开启"
            
        case .poweredOn:
            central.scanForPeripherals(withServices: nil, options: nil)
        @unknown default:
            break
        }
        print(message)
    }
    
    //    2
    public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        
        print("设备名-->" (peripheral.name ?? ""))
        let peripheralName = peripheral.name ?? ""
        let contains = (self.currentScanName != nil) && peripheralName.contains(self.currentScanName!)

        if contains {
            self.delegate?.discoverPeripheral(peripheral)
        }
    }
    //    3
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        //        链接成功之后 停止扫描
        central.stopScan()
        //蓝牙连接成功
        if currentPeripheral != nil {
            currentPeripheral!.delegate = self
            currentPeripheral!.discoverServices([CBUUID(string: self.currentServiceUUID!)])
        }
        successBlock?()

    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        print("断开链接 设备 =(peripheral.name ?? "")")
    }
}
//MARK: - 蓝牙外围设备代理方法
extension BaseManager: CBPeripheralDelegate {
    //发现服务
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        print("当前currentServiceUUID-->" (self.currentServiceUUID ?? ""))
        for service in peripheral.services! {
            print("寻找服务,服务有:(service)" "   id-->" service.uuid.uuidString)
            if service.uuid.uuidString == self.currentServiceUUID {
                peripheral.discoverCharacteristics(nil, for: service)
                print("找到当前服务了。。。。")
                break
            }
        }
    }
    
    //发现特征
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        for characteristic in service.characteristics! {
            print("特征有(characteristic)")
            if characteristic.uuid.uuidString == self.currentWriteUUID {
                self.writeCharacteristic = characteristic
                print("-找到了写服务----(characteristic)")
            }
        }
    }
    
    ///发送指令给打印机
    private func send(value:Data) {
        currentPeripheral!.writeValue(value, for: writeCharacteristic, type: CBCharacteristicWriteType.withResponse)
    }
    ///发送指令
    func sendCommand(_ data:Data) {
        send(value: data)
    }
}

// MARK: 打印机封装方法
class BTPrinter
{
    ///一行最多打印字符个数
    let kRowMaxLength = 32
    let ESC:UInt8   = 27    //换码 0x1B
    let FS:UInt8    = 28    //文本分隔符 0x1c
    let GS:UInt8    = 29    //组分隔符
    let DLE:UInt8   = 16    //数据连接换码
    let EOT:UInt8   = 4     //传输结束
    let ENQ:UInt8   = 5     //询问字符
    let SP:UInt8    = 32    //空格
    let HT:UInt8    = 9     //横向列表
    let LF:UInt8    = 10    //打印并换行(水平定位)
    let CR:UInt8    = 13    //归位键
    let FF:UInt8    = 12    //走纸控制(打印并回到标准模式(在页模式下) )
    
    /*------------------------------------------------------------------------------------------------*/
    //    2019.7.24 新增方法
    /*------------------------------------------------------------------------------------------------*/

    let enc = CFStringConvertEncodingToNSStringEncoding(UInt32(CFStringEncodings.GB_18030_2000.rawValue))

    //   text 内容。value 右侧内容 左侧列 支持的最大显示 超过四字自动换行
    func setText(text: String,value: String,maxChar:Int)->Data {
        let data = text.data(using: String.Encoding(rawValue: enc))! as NSData

        if (data.length > maxChar) {
            let lines = data.length / maxChar
            let remainder = data.length % maxChar
            var tempData: NSMutableData = NSMutableData.init()
            for i in 0..<lines {
                let temp = (data.subdata(with: NSMakeRange(i*maxChar, maxChar)) as NSData)
                tempData.append(temp.bytes, length: temp.length)
                if i == 0 {
                    let data = setOffsetText(value: value)
                    tempData.append(data.bytes, length: data.length)
                }
                let line = nextLine(number: 1) as NSData
                tempData.append(line.bytes, length: line.length)
            }
            if remainder != 0 { // 余数不0
                let temp = data.subdata(with: NSMakeRange(lines*maxChar, remainder)) as NSData
                tempData.append(temp.bytes, length: temp.length)
            }
            return tempData as Data
        }
        let rightTextData = setOffsetText(value: value)
        let mutData = NSMutableData.init(data: data as Data)
        mutData.append(rightTextData.bytes, length: rightTextData.length)
        return mutData as Data
    }

    // 添加文字,不换行
    func setTitle(text: String) -> Data {
        let enc = CFStringConvertEncodingToNSStringEncoding(UInt32(CFStringEncodings.GB_18030_2000.rawValue))
        ///这里一定要GB_18030_2000,测试过用utf-系列是乱码,踩坑了。
        let data = text.data(using: String.Encoding(rawValue: enc), allowLossyConversion: false)
        if data != nil{
            return data!
        }
        return Data()
    }
    /**
     *  设置偏移文字
     *
     *  @param value 右侧内容
     */
    func setOffsetText(value: String) -> NSData {
        let attributes = [NSAttributedString.Key.font:UIFont.systemFont(ofSize: 22.0)] //设置字体大小
        let option = NSStringDrawingOptions.usesLineFragmentOrigin
        //获取字符串的frame
        let rect:CGRect = value.boundingRect(with: CGSize.init(width: 320.0, height: 999.9), options: option, attributes: attributes, context: nil)
        let valueWidth: Int = Int(rect.size.width)
        let preNum = (UserDefaults.standard.value(forKey: "printerNum") ?? 0) as! Int
        let offset = (preNum == 0 ? 384 : 566) - valueWidth
        let remainder = offset % 256
        let consult = offset / 256;
        var foo:[UInt8] = [0x1B, 0x24]
        foo.append(UInt8(remainder))
        foo.append(UInt8(consult))
        let data = Data.init(bytes: foo) as NSData
        let mutData = NSMutableData.init()
        mutData.append(data.bytes, length: data.length)
        let titleData = setTitle(text: value) as NSData
        mutData.append(titleData.bytes, length: titleData.length)
        return mutData as NSData
    }
    /*------------------------------------------------------------------------------------------------*/

    ///初始化打印机
    func clear() -> Data {
        return Data.init([ESC, 64])
    }
    
    ///打印空格
    func printBlank(number:Int) -> Data {
        var foo:[UInt8] = []
        for _ in 0..<number {
            foo.append(SP)
        }
        return Data.init(foo)
    }
    
    ///换行
    func nextLine(number:Int) -> Data {
        var foo:[UInt8] = []
        for _ in 0..<number {
            foo.append(LF)
        }
        return Data.init(foo)
    }
    
    ///绘制下划线
    func printUnderline() -> Data {
        var foo:[UInt8] = []
        foo.append(ESC)
        foo.append(45)
        foo.append(1)//一个像素
        return Data.init(foo)
    }
    
    ///取消绘制下划线
    func cancelUnderline() -> Data {
        var foo:[UInt8] = []
        foo.append(ESC)
        foo.append(45)
        foo.append(0)
        return Data.init(foo)
    }
    
    ///加粗文字
    func boldOn() -> Data {
        var foo:[UInt8] = []
        foo.append(ESC)
        foo.append(69)
        foo.append(0xF)
        return Data.init(foo)
    }
    
    ///取消加粗
    func boldOff() -> Data {
        var foo:[UInt8] = []
        foo.append(ESC)
        foo.append(69)
        foo.append(0)
        return Data.init(foo)
    }
    
    ///左对齐
    func alignLeft() -> Data {
        return Data.init([ESC,97,0])
    }
    
    ///居中对齐
    func alignCenter() -> Data {
        return Data.init([ESC,97,1])
    }
    
    ///右对齐
    func alignRight() -> Data {
        return Data.init([ESC,97,2])
    }
    
    ///水平方向向右移动col列
    func alignRight(col:UInt8) -> Data {
        var foo:[UInt8] = []
        foo.append(ESC)
        foo.append(68)
        foo.append(col)
        foo.append(0)
        return Data.init(foo)
    }

    ///字体变大为标准的n倍
    func fontSize(font:Int8) -> Data {
        var realSize:UInt8 = 0
        switch font {
        case 1:
            realSize = 0
        case 2:
            realSize = 17
        case 3:
            realSize = 34
        case 4:
            realSize = 51
        case 5:
            realSize = 68
        case 6:
            realSize = 85
        case 7:
            realSize = 102
        case 8:
            realSize = 119
        default:
            break
        }
        var foo:[UInt8] = []
        foo.append(29)
        foo.append(33)
        foo.append(realSize)
        return Data.init(foo)
    }
    
    ///进纸并全部切割
    func feedPaperCutAll() -> Data {
        var foo:[UInt8] = []
        foo.append(GS)
        foo.append(86)
        foo.append(65)
        foo.append(0)
        return Data.init(foo)
    }
    
    ///进纸并切割(左边留一点不切)
    func feedPaperCutPartial() -> Data {
        var foo:[UInt8] = []
        foo.append(GS)
        foo.append(86)
        foo.append(66)
        foo.append(0)
        return Data.init(foo)
    }
    
    ///设置纸张间距为默认
    func mergerPaper() -> Data {
        return Data.init([ESC,109])
    }
}

-End-

0 人点赞