Clients often come with an unreadable QR code after adding a logo. One restaurant lost 20% of clicks due to an incorrect error correction level—we fixed it in two days, and readability jumped to 99.9%. According to Statista, transaction volume via QR codes exceeds $12 billion, and custom QR codes increase conversion by 30% on average. In this article, we'll cover generation, customization, and export of QR codes on iOS and Android with code and proven practices. Our engineers have 10+ years of experience in mobile development, so we share only working solutions. Using a branded QR code reduces printing costs by up to 25% by eliminating additional media.
Problems We Solve
Two common issues we encounter:
Logo placement without readability loss. Adding a logo to a QR code without proper error correction can render it unscannable. We always use level H (30% recovery) and keep the logo under 25% of the QR area.
Color and style mismatch with brand guidelines. Standard black-and-white QR codes don't fit every design. We customize colors and even module shapes (rounded, dotted) while maintaining scan reliability.
How We Do It: Technical Details
We use native APIs and open-source libraries for efficiency.
iOS: CIFilter and Customization
Basic generation with CoreImage:
func generateQRCode(from string: String) -> UIImage? { guard let data = string.data(using: .utf8), let filter = CIFilter(name: "CIQRCodeGenerator") else { return nil } filter.setValue(data, forKey: "inputMessage") filter.setValue("H", forKey: "inputCorrectionLevel") // H = 30% correction — needed with logo guard let ciImage = filter.outputImage else { return nil } // Scale without blur via transform, not UIImage(ciImage:) with resize let scale = 10.0 let scaledImage = ciImage.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) let context = CIContext() guard let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) else { return nil } return UIImage(cgImage: cgImage) } CIQRCodeGenerator always produces black-and-white output. For color, replace white/black using CIFalseColor or draw over with UIGraphicsImageRenderer.
Logo in center. Correction level H allows covering up to 30% of the QR area. Logo size ~25% works reliably. Overlay with UIGraphicsImageRenderer:
func addLogo(_ logo: UIImage, to qrImage: UIImage) -> UIImage { let renderer = UIGraphicsImageRenderer(size: qrImage.size) return renderer.image { _ in qrImage.draw(in: CGRect(origin: .zero, size: qrImage.size)) let logoSize = CGSize(width: qrImage.size.width * 0.25, height: qrImage.size.height * 0.25) let logoOrigin = CGPoint( x: (qrImage.size.width - logoSize.width) / 2, y: (qrImage.size.height - logoSize.height) / 2 ) logo.draw(in: CGRect(origin: logoOrigin, size: logoSize)) } } Rounded modules require parsing the bit matrix and drawing each module manually via UIBezierPath. The QRCode Swift Package does this and allows extensive customization.
Android: ZXing and ML Kit
ZXing is the de facto standard:
import com.google.zxing.BarcodeFormat import com.google.zxing.qrcode.QRCodeWriter fun generateQRCode(content: String, size: Int): Bitmap { val writer = QRCodeWriter() val bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, size, size) val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565) for (x in 0 until size) { for (y in 0 until size) { bitmap.setPixel(x, y, if (bitMatrix[x, y]) Color.BLACK else Color.WHITE) } } return bitmap } For custom colors, replace Color.BLACK/Color.WHITE. For logo, overlay via Canvas:
fun addLogoToBitmap(qrBitmap: Bitmap, logo: Bitmap): Bitmap { val result = qrBitmap.copy(Bitmap.Config.ARGB_8888, true) val canvas = Canvas(result) val logoSize = qrBitmap.width / 4 val left = (qrBitmap.width - logoSize) / 2f val top = (qrBitmap.height - logoSize) / 2f val scaledLogo = Bitmap.createScaledBitmap(logo, logoSize, logoSize, true) canvas.drawBitmap(scaledLogo, left, top, null) return result } ML Kit Barcode Scanning is from Google and supports multiple formats (QR, EAN, Code 128, Data Matrix). It's for scanning only, but often used alongside generation.
Case Study: Restaurant Menu QR
One restaurant approached us with a static QR code that was unreadable after printing with a large logo. We regenerated the QR with correction level H and reduced the logo to 25% of the code area. Testing on 50 devices showed 99.5% readability even in low light. The fix took two days.
Why Correction Level H Matters
Level H recovers up to 30% of data. This is critical when placing a logo on the QR code. Without sufficient correction, even a small image can break readability. We use H in all projects with logos; for plain QR codes, M or L is sufficient.
Ensuring Readability with Logo
Besides H, maintain a white margin (quiet zone) of at least 4 modules around the QR. Logo size should not exceed 25% of QR area. Our tests on 50 devices show 99.5% readability under these rules.
Static vs Dynamic QR Comparison
| Feature | Static QR | Dynamic QR |
|---|---|---|
| URL | Hardcoded | Changeable via server |
| UTM tags | Fixed | Dynamic |
| Analytics | None built-in | Server logs available |
| Lifespan | Unlimited | As long as redirect server runs |
| Generation | Client or server | Server-side, image cached in CDN |
For marketing, dynamic QR codes offer flexibility and analytics. On mobile client, they only display a ready PNG or <img> tag via WebView.
Saving and Sharing
iOS: Save to gallery via UIImageWriteToSavedPhotosAlbum or PHPhotoLibrary.shared().performChanges. Requires NSPhotoLibraryAddUsageDescription. Share via UIActivityViewController with UIImage.
Android 10+: Use MediaStore.Images.Media.insertImage via ContentResolver (no WRITE_EXTERNAL_STORAGE). Share via Intent.ACTION_SEND with URI through FileProvider.
What's Included in Our Work
- Source code for generation and customization module
- API documentation and integration guide
- Backend setup for dynamic QR codes (if needed)
- Testing on 5+ real device models
- Deployment to App Store / Google Play (TestFlight, Google Play Console)
Process and Timelines
- Requirements gathering – understand use case, brand guidelines, size constraints.
- Prototype – generate sample QR with logo, test readability.
- Development – implement module with chosen customization level.
- Testing – verify on multiple devices and lighting conditions.
- Delivery – provide code and documentation.
Basic generation: 1 business day. Custom style with logo and dynamic backend: 2–3 days. Cost is determined after analysis.
Our engineers have completed over 50 projects with QR generation—from simple business cards to branded promotional campaigns. If you need a QR code with non-standard requirements, contact us for an assessment. Order QR module integration—it takes from 1 day.







