前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实战分享:Swift 蓝牙打印机数据排版

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

作者头像
网罗开发
发布2021-09-29 15:10:24
1.2K0
发布2021-09-29 15:10:24
举报
文章被收录于专栏:网罗开发

前言

蓝牙打印机打印排版 本次使用的是 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-

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 网罗开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 功能包含:
    • 效果图
    • 使用方法
    • 核心代码
    • 代码中使用的数字含义
    • 打印机支持的指令
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档