A modern QR Code scanner framework for iOS with comprehensive SwiftUI and UIKit support. Delivers a native iOS scanning experience with advanced customization options. Written in Swift.
iPhone native camera | QRScanner implementation |
---|---|
![]() |
![]() |
"QR Code" is a registered trademark of DENSO WAVE INCORPORATED
- 🎯 Native iOS Design - Matches iPhone's built-in camera scanning experience
- 🚀 SwiftUI & UIKit Support - Full compatibility with both modern and traditional iOS development
- 📱 iOS 14.0+ - Built for modern iOS with latest Swift features
- ⚡ Easy Integration - Simple setup with comprehensive examples
- 🎨 Highly Customizable - Extensive configuration options for focus frame, animations, and effects
- 💡 Production Ready - Battle-tested in Mercari's production apps
Quick Start: SwiftUI Example | UIKit Example
- iOS: 14.0+
- Swift: 5.9+
- Xcode: 16.0+
- File → Add Package Dependencies
- Enter:
https://github.com/mercari/QRScanner.git
- Select version and add to your target
dependencies: [
.package(url: "https://github.com/mercari/QRScanner.git", .upToNextMajor(from: "2.0.0"))
]
import QRScanner
Add camera usage description to your Info.plist
:
<key>NSCameraUsageDescription</key>
<string>Camera access is required for QR code scanning</string>
- SwiftUI: Modern declarative UI (recommended for new projects)
- UIKit: Traditional imperative UI (for existing projects)
Complete examples: SwiftUI Sample | UIKit Sample
💡 Recommended for new projects - SwiftUI provides a more modern and concise API
import SwiftUI
import QRScanner
import AVFoundation
struct ContentView: View {
@State private var scannedCode = ""
@State private var isPresented = false
var body: some View {
VStack {
Button("Start Scanning") {
isPresented = true
}
if !scannedCode.isEmpty {
Text("Scanned: \(scannedCode)")
}
}
.sheet(isPresented: $isPresented) {
QRScannerSwiftUIView(
onSuccess: { code in
scannedCode = code
isPresented = false
},
onFailure: { error in
print("Scan error: \(error)")
isPresented = false
}
)
}
}
}
struct AdvancedQRScannerView: View {
@State private var scannedCode = ""
@State private var isScanning = true
@State private var torchActive = false
var body: some View {
VStack {
QRScannerSwiftUIView(
configuration: .init(
focusImagePadding: 12.0,
animationDuration: 0.3,
isBlurEffectEnabled: true
),
isScanning: $isScanning,
torchActive: $torchActive,
onSuccess: { code in
scannedCode = code
isScanning = false
},
onFailure: { error in
print("Error: \(error.localizedDescription)")
}
)
HStack {
Button(isScanning ? "Pause" : "Resume") {
isScanning.toggle()
}
Button("Torch") {
torchActive.toggle()
}
}
.padding()
}
}
}
Parameter | Type | Default | Description |
---|---|---|---|
focusImage |
UIImage? |
nil |
Custom focus frame image |
focusImagePadding |
CGFloat |
8.0 |
Focus frame padding |
animationDuration |
Double |
0.5 |
Animation duration |
isBlurEffectEnabled |
Bool |
false |
Enable blur effect |
Binding | Type | Description |
---|---|---|
isScanning |
Bool |
Control scanning state |
torchActive |
Bool |
Control torch state |
Callback | Description |
---|---|
onSuccess |
Called when QR code is successfully scanned |
onFailure |
Called when scanning fails |
onTorchActiveChange |
Called when torch state changes |
📘 Complete Example: QRScannerSwiftUISample
🔧 For existing projects - UIKit integration with full customization support
import QRScanner
import AVFoundation
final class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupQRScanner()
}
private func setupQRScanner() {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
setupQRScannerView()
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
if granted {
DispatchQueue.main.async { [weak self] in
self?.setupQRScannerView()
}
}
}
default:
showAlert()
}
}
private func setupQRScannerView() {
let qrScannerView = QRScannerView(frame: view.bounds)
view.addSubview(qrScannerView)
qrScannerView.configure(delegate: self, input: .init(isBlurEffectEnabled: true))
qrScannerView.startRunning()
}
private func showAlert() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
let alert = UIAlertController(title: "Error", message: "Camera is required to use in this application", preferredStyle: .alert)
alert.addAction(.init(title: "OK", style: .default))
self?.present(alert, animated: true)
}
}
}
extension ViewController: QRScannerViewDelegate {
func qrScannerView(_ qrScannerView: QRScannerView, didFailure error: QRScannerError) {
print(error)
}
func qrScannerView(_ qrScannerView: QRScannerView, didSuccess code: String) {
print(code)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let qrScannerView = QRScannerView(frame: view.bounds)
// Customize focusImage, focusImagePadding, animationDuration
qrScannerView.focusImage = UIImage(named: "scan_qr_focus")
qrScannerView.focusImagePadding = 8.0
qrScannerView.animationDuration = 0.5
qrScannerView.configure(delegate: self)
view.addSubview(qrScannerView)
qrScannerView.startRunning()
}
Setup Custom Class | Customize |
---|---|
![]() |
![]() |
Example: FlashButton.swift
final class ViewController: UIViewController {
...
@IBOutlet var flashButton: FlashButton!
@IBAction func tapFlashButton(_ sender: UIButton) {
qrScannerView.setTorchActive(isOn: !sender.isSelected)
}
}
extension ViewController: QRScannerViewDelegate {
...
func qrScannerView(_ qrScannerView: QRScannerView, didChangeTorchActive isOn: Bool) {
flashButton.isSelected = isOn
}
}
qrScannerView.configure(delegate: self, input: .init(isBlurEffectEnabled: true))
📘 Complete Example: QRScannerSample
- Hitsu (@hitsubunnu)
- Sonny (@tedbrosby)
- Daichiro (@daichiro)
Please read the CLA carefully before submitting your contribution to Mercari. Under any circumstances, by submitting your contribution, you are deemed to accept and agree to be bound by the terms and conditions of the CLA.
Copyright 2019 Mercari, Inc.
Licensed under the MIT License.