Share Notes

chundev

View the Project on GitHub latteouka/share-notes

QR Code 純文字掃描在 iPhone 變成「撥號」——成因與解法

日期:2026-05-30 情境:用純文字 QR Code 承載一段資訊,希望「掃了直接顯示」


TL;DR

把一段含長數字(如編號 123456789)的純文字做成 QR Code,iPhone 相機掃描後不顯示文字,反而跳出撥打電話的選項。根因是手機相機的資料偵測器(iOS 的 NSDataDetector、Android 的 Smart Linkify / Google Lens)會把「連續 7 碼以上的數字」判定成電話號碼。

關鍵雷點:把數字改成全形(123)沒用、用空格或 - 分隔也沒用。 唯一可靠的解法是在數字串中間插入「非數字、且不是電話分隔符」的字元(中文字或英文字母),讓任何一段連續的半形數字都不足 7 碼。


背景

QR Code 本身只是一段靜態資料。手機相機掃到內容後,作業系統會主動對這段文字做「資料偵測」,命中電話、網址、日期、地址等樣式時就提供對應的快捷動作。

第三種就是這次踩到的坑:明明是純文字,卻被「智慧」辨識成電話。


問題 / 症狀

QR 內容(純文字):
編號:123456789
名稱:DEMO
狀態:進行中
...

iPhone 掃描結果:
→ 不顯示文字,跳出「撥打 123456789」的提示

最有搜尋價值的關鍵字:QR code plain text iPhone wants to call / 純文字 QR 掃描跳撥號


除錯過程

嘗試 做法 結果 原因
❌ 1 把編號改成全形數字 123456789 仍跳撥號 偵測器在比對前會做 NFKC 正規化,把全形相容字元折回半形 ASCII,等於沒改
❌ 2 空格/連字號分隔 123-456-789123 456 789 仍跳撥號 電話號碼解析器(libphonenumber 那套)把空格、-.()+ 都視為合法分隔符,照樣串成一個號碼
✅ 3 在數字中插入中文字或英文字母 123年456789號 正常顯示文字 中文字/字母不是數字也不是電話分隔符,連續半形數字被切到不足 7 碼,偵測器不再命中

解法 / 核心知識

原則:QR 純文字中,任何一處都不能出現約 7 碼以上的連續半形數字。

要打斷數字串,插入的字元必須同時滿足:

  1. 不是數字(全形數字無效,會被 NFKC 正規化還原)
  2. 不是電話分隔符(空格 / - / . / ( ) / + 都無效)

✅ 有效的打斷字元:中文字、英文字母、/(部分情境) ❌ 無效:全形數字、空格、-.()+

範例(Python,用 segno):

import segno, re

# 把長編號用語意化方式打斷,例如 "123456789" → "123年456789號"
content = "編號:123年456789號\n名稱:DEMO\n狀態:進行中"

# 自我驗證:全文不可有 >=7 碼連續半形數字
assert not re.findall(r"\d{7,}", content), "仍有長數字串會被當電話"

segno.make(content, error="m").save("out.png", scale=10, border=4)

⚠️ 連「括號註記原始值」都不行——只要文字裡再出現一次完整的 9 碼數字,就會再次觸發。原始數值要保留就放在系統內,別印進 QR。

這個 assert 是關鍵:把「不可有長數字串」變成可程式驗證的條件,產生前先檢查,比掃描後才發現失敗可靠得多。


順帶一提:日期時間通常安全

5/17/2026, 12:11:42 PM 這種時間戳,因為被 /:,、空格切成多個短段,沒有 7 碼以上連續數字,不會被判定成電話。但它可能被偵測成「日期」而提供「加入行事曆」——若連這也不想要,同樣用非數字字元打斷即可。


延伸:繁體中文 QR 能裝多少字?

順手量了繁體中文的容量。QR 中文走 byte mode(UTF-8,每字 3 bytes),容量隨糾錯等級遞減(以最大 Version 40 計):

糾錯等級 容錯能力 繁中字上限
L ~7% 984 字
M(常用預設) ~15% 777 字
Q ~25% 554 字
H ~30% 424 字

⚠️ 兩個容易誤判的點:


學到的事


參考資料