Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save PetrusM/267e2ee8c1d8b5dca17eac085afa7d7c to your computer and use it in GitHub Desktop.
Save PetrusM/267e2ee8c1d8b5dca17eac085afa7d7c to your computer and use it in GitHub Desktop.
AVKit : Get binary data from QRCode
import Foundation
import AVKit
// This extension is based upon https://www.thonky.com/qr-code-tutorial/data-encoding
extension AVMetadataMachineReadableCodeObject
{
var binaryValue: Data?
{
switch type
{
// TODO Add cases for PDF417 and DataMatrix
case .qr:
return removeQrProtocolData(binaryValueWithProtocol!)
case .aztec:
guard let string = stringValue else
{ return nil }
return string.data(using: String.Encoding.isoLatin1)
default:
return nil
}
}
var binaryValueWithProtocol: Data?
{
guard let descriptor = descriptor else
{
return nil
}
switch type
{
case .qr:
return (descriptor as! CIQRCodeDescriptor).errorCorrectedPayload
case .aztec:
return (descriptor as! CIAztecCodeDescriptor).errorCorrectedPayload
case .pdf417:
return (descriptor as! CIPDF417CodeDescriptor).errorCorrectedPayload
case .dataMatrix:
return (descriptor as! CIDataMatrixCodeDescriptor).errorCorrectedPayload
default:
return nil
}
}
private func removeQrProtocolData(_ input: Data) -> Data?
{
var halves = input.halfBytes()
var batch = takeBatch(&halves)
var output = batch
while !batch.isEmpty
{
batch = takeBatch(&halves)
output.append(contentsOf: batch)
}
return Data(output)
}
private func takeBatch(_ input: inout [HalfByte]) -> [UInt8]
{
let version = (descriptor! as! CIQRCodeDescriptor).symbolVersion
let characterCountLength = version > 9 ? 16 : 8
let mode = input.remove(at: 0)
var output = [UInt8]()
switch (mode.value)
{
// TODO If there is not only binary in the QRCode, then cases should be added here.
case 0x04: // Binary
let charactersCount: UInt16
if characterCountLength == 8
{
charactersCount = UInt16(input.takeUInt8())
}
else
{
charactersCount = UInt16(input.takeUInt16())
}
for _ in 0..<charactersCount
{
output.append(input.takeUInt8())
}
return output
case 0x00: // End of data
return []
default:
return []
}
}
}
fileprivate struct HalfByte
{
let value: UInt8
}
fileprivate extension [HalfByte]
{
mutating func takeUInt8() -> UInt8
{
let left = self.remove(at: 0)
let right = self.remove(at: 0)
return UInt8(left, right)
}
mutating func takeUInt16() -> UInt16
{
let first = self.remove(at: 0)
let second = self.remove(at: 0)
let third = self.remove(at: 0)
let fourth = self.remove(at: 0)
return UInt16(first, second, third, fourth)
}
}
fileprivate extension Data
{
func halfBytes() -> [HalfByte]
{
var result = [HalfByte]()
self.forEach
{ (byte: UInt8) in result.append(contentsOf: byte.halfBytes()) }
return result
}
init(_ halves: [HalfByte])
{
var halves = halves
var result = [UInt8]()
while halves.count > 1
{
result.append(halves.takeUInt8())
}
self.init(result)
}
}
fileprivate extension UInt8
{
func halfBytes() -> [HalfByte]
{
[HalfByte(value: self >> 4), HalfByte(value: self & 0x0F)]
}
init(_ left: HalfByte, _ right: HalfByte)
{
self.init((left.value << 4) + (right.value & 0x0F))
}
}
fileprivate extension UInt16
{
init(_ first: HalfByte, _ second: HalfByte, _ third: HalfByte, _ fourth: HalfByte)
{
let first = UInt16(first.value) << 12
let second = UInt16(second.value) << 8
let third = UInt16(third.value) << 4
let fourth = UInt16(fourth.value) & 0x0F
let result = first + second + third + fourth
self.init(result)
}
}
@PetrusM
Copy link
Author

PetrusM commented Oct 26, 2022

Notes :

  • It uses CIQRCodeDescriptor, so it is only for iOS >= 11
  • But not using any internal Apple API, so suitable for the App store.

For previous versions, but not suitable for App Store, mix with following gist : https://gist.github.com/nfarshchi/7039f0cf933424fcb7361e4f70e4cebe

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment