SmartMembership: Building a Privacy-First Digital Loyalty Wallet on iOS

By June Hong •

Introduction

We all have that one drawer (or wallet) overflowing with loyalty cards, gym passes, and library cards. While there are many apps to digitize these, many come with bloat: account requirements, cloud syncing, and data tracking.

SmartMembership is a lightweight, open-source iOS application designed to solve this simply and privately. It allows users to scan, store, and regenerate their membership cards entirely on-device.

Download on the App Store

In this post, I'll walk through the technical challenges of building a robust barcode scanner and renderer using native iOS frameworks.


The Problem: Scanning vs. Rendering

Digitizing a card seems simple: take a picture, show the picture. However, photos of barcodes are often hard for laser scanners to read due to glare or screen reflections.

To ensure a 100% success rate at checkout, the app needs to: 1. Scan the barcode data from the physical card. 2. Store the raw string data and format type. 3. Regenerate a crisp, high-contrast digital barcode on demand.


Technical Deep Dive

1. Universal Scanning with AVFoundation

We used AVFoundation for real-time scanning. While the framework is powerful, we encountered a specific nuance with UPC-A barcodes.

UPC-A is common in North America (12 digits). However, iOS's VNBarcodeObservation often detects these as EAN-13 (13 digits) by adding a leading zero.

To ensure data consistency, we implemented specific logic to normalize this:

// Logic to handle iOS detecting UPC-A as EAN-13
if type == .ean13 && code.hasPrefix("0") && code.count == 13 {
    // Strip leading zero to convert back to UPC-A
    return String(code.dropFirst())
}

2. Robust Rendering & The Fallback Strategy

For rendering, we utilized CoreImage filters (e.g., CICode128BarcodeGenerator, CIEAN13BarcodeGenerator).

A major challenge arose with checksums. EAN-13 and UPC-A formats require the last digit to be a mathematically valid checksum of the previous digits. If a user manually enters a number or a legacy card has a non-standard format, the Core Image filter returns nil.

To prevent the user from being stuck with an un-displayable card, we implemented a Smart Fallback mechanism:

If the specific format generation (EAN-13/UPC) fails, the app automatically falls back to generating a Code 128 barcode.

Code 128 is highly dense and supports any ASCII character set, making it a safe "catch-all" that most modern POS scanners can read.

3. Privacy with SwiftData

Since the goal was a digital wallet, not a data harvesting tool, we opted for SwiftData for local persistence. There is no backend, no analytics, and no internet permission required.


Conclusion

SmartMembership demonstrates that useful, everyday utilities can be built without compromising privacy or relying on heavy third-party SDKs. By leveraging AVFoundation and CoreImage intelligently, we created a seamless bridge between physical cards and digital wallets.

Download on the App Store