I have a string for which I want to generate a barcode using a specific barcode symbology: codabar.
I can generate a barcode, but it uses the wrong barcode symbology causing it to be read incorrectly by scanners.
func generateBarcode(from string: String) -> Image {
let context = CIContext()
let generator = CIFilter.code128BarcodeGenerator()
generator.message = Data(string.utf8)
if let outputImage = generator.outputImage,
let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
let uiImage = UIImage(cgImage: cgImage)
return Image(uiImage: uiImage)
}
return Image(systemName: "barcode")
}
I have a barcode font and tried to convert the string using that, but that doesn't work. (there is a small chance the font I'm using is incomplete or otherwise "wrong", I did get an error loading it when trying some other stuff)
Text("A25490001104784B").font(.custom("codabar", size: 18))
How can I generate a barcode using a specific barcode symbology (codabar)?
Based on the answer in this question I tried to generate Codabar barcodes using the RSBarcodes_Swift library. I didn't get it working using the usage example from the README, it gave an No code generator selected.
message.
RSUnifiedCodeGenerator.shared.generateCode("2166529V", machineReadableCodeObjectType: AVMetadataObject.ObjectType.codabar.rawValue)
This is because the Codabar generator is not listed in the RSUnifiedCodeGenerator.generateCode
method.
Instead of trying to use the RSCodaBarGenerator directly I decided to not use the library and instead copy and alter the code necessary to generate Codabar barcodes.
import Foundation
import UIKit
import AVFoundation
open class CodabarGenerator {
private let codabarAlphabetString = "0123456789-$:/.+ABCD"
// swiftlint:disable:next cyclomatic_complexity
private func encodeCharacterString(_ characterString: String) -> String {
switch characterString {
case "0":
return "1010100110"
case "1":
return "1010110010"
case "2":
return "1010010110"
case "3":
return "1100101010"
case "4":
return "1011010010"
case "5":
return "1101010010"
case "6":
return "1001010110"
case "7":
return "1001011010"
case "8":
return "1001101010"
case "9":
return "1101001010"
case "ー":
return "1010011010"
case "$":
return "1011001010"
case ":":
return "11010110110"
case "/":
return "11011010110"
case ".":
return "11011011010"
case "+":
return "10110110110"
case "A":
return "10110010010"
case "B":
return "10010010110"
case "C":
return "10100100110"
case "D":
return "10100110010"
default:
fatalError("Invalid character \(characterString) for Codabar encoding")
}
}
func isValid(_ contents: String) -> Bool {
if contents.count > 0
&& contents.range(of: "A") == nil
&& contents.range(of: "B") == nil
&& contents.range(of: "C") == nil
&& contents.range(of: "D") == nil {
for character in contents {
let location = codabarAlphabetString.firstIndex(of: character)
if location == nil {
return false
}
}
return true
} else {
return false
}
}
func initiator() -> String {
self.encodeCharacterString("A")
}
func terminator() -> String {
self.encodeCharacterString("B")
}
func barcode(_ contents: String) -> String {
var barcode = ""
for character in contents {
barcode += self.encodeCharacterString(String(character))
}
return initiator() + barcode + terminator()
}
let barcodeDefaultHeight = 28
var fillColor: UIColor = UIColor.white
var strokeColor: UIColor = UIColor.black
func drawCompleteBarcode(_ completeBarcode: String, targetSize: CGSize? = nil) -> UIImage? {
let length: Int = completeBarcode.count
if length <= 0 {
return nil
}
// Values taken from CIImage generated AVMetadataObjectTypePDF417Code type image
// Top spacing = 1.5
// Bottom spacing = 2
// Left & right spacing = 2
let width = length + 4
// Calculate the correct aspect ratio, so that the resulting image can be resized to the target size
var height = barcodeDefaultHeight
if let targetSize = targetSize {
height = Int(targetSize.height / targetSize.width * CGFloat(width))
}
let size = CGSize(width: CGFloat(width), height: CGFloat(height))
UIGraphicsBeginImageContextWithOptions(size, false, 1)
if let context = UIGraphicsGetCurrentContext() {
context.setShouldAntialias(false)
self.fillColor.setFill()
self.strokeColor.setStroke()
context.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
context.setLineWidth(1)
// swiftlint:disable:next identifier_name
var i = 0
for char in completeBarcode {
i += 1
if char == "1" {
// swiftlint:disable:next identifier_name
let x = i + (2 + 1)
context.move(to: CGPoint(x: CGFloat(x), y: 1.5))
context.addLine(to: CGPoint(x: CGFloat(x), y: size.height - 2))
}
}
context.drawPath(using: CGPathDrawingMode.fillStroke)
let barcode = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return barcode
} else {
return nil
}
}
}
Using the above code barcodes can be generated like this.
func generateBarcode(contents: String) -> Image {
let codabarGenerator = CodabarGenerator()
let encodedBarcode = codabarGenerator.barcode(contents)
if let barcodeImage = codabarGenerator.drawCompleteBarcode(encodedBarcode) {
return Image(uiImage: barcodeImage
} else {
return Image(systemName: "xmark.circle")
}
}