iPhone 13 mini 固有のカメラフォーカス問題と向き合った話

| 開発記録 | QRリーダー

タグ: #iOS #カメラ #デバッグ

「iPhone 13 mini だけ QR コードが読み取れない」という低評価レビューが続きました。手元の検証機ではぜんぜん再現しません。原因は本体のカメラのフォーカス挙動で、CocoaPods の post-install フックでライブラリにパッチを当てて直しました。冪等性で自分の罠にはまった話も書きます。

レビューの違和感

QR コードリーダーアプリに、まったく読み取れないという低評価レビューが続いた時期がありました。よくよく見ると、報告者の機種は決まって iPhone 13 mini です。手元の検証機(iPhone 12, 14, 15)ではぜんぜん再現しません。原因が分からないまま星だけが減っていく。しんどい時間でした。

同一コードで挙動が違う理由

調べていくと、iPhone 13 mini のリアカメラには癖がありました。特定のフォーカスモードでオートフォーカスが効きにくいのです。QR スキャンライブラリ(mobile_scanner / mlkit)が要求するモードと、13 mini の動作が噛み合っていませんでした。

手元に実機はありません。まずはレビューと Crashlytics から拾える情報、それにライブラリ側の issue を漁りました。たどり着いた回避策はこれです。フォーカスモードを continuousAutoFocus ではなく autoFocus にして、距離を指定する。

Pods 自動パッチという手段

問題は、ライブラリ本体のコードをいじる必要があったことです。フォーク版を作ってメンテし続けるのは、たぶん続きません。そこで CocoaPods の post-install フックでパッチを当てる方式にしました。

post_install do |installer|
  # ライブラリの該当 Swift ファイルに修正を当てる
  patch_camera_focus(installer)
end

パッチの内容は最小限です。該当箇所を正規表現で検出して、修正コードを差し込むだけ。

実際の Podfile はこんな雰囲気です。対象 Pod の Swift ファイルを File.readgsubFile.write するだけ。冪等性を保つため、修正後の行にも再マッチするパターンを使います。

post_install do |installer|
  installer.pods_project.targets.each do |t|
    next unless t.name == 'mobile_scanner'
    swift_file = File.join(installer.sandbox.root, 'mobile_scanner',
                           'ios', 'Classes', 'MobileScannerPlugin.swift')
    next unless File.exist?(swift_file)
    source = File.read(swift_file)
    patched = source.gsub(
      /focusMode = \.continuousAutoFocus[^\n]*\n/,
      "focusMode = .autoFocus // patched for iPhone 13 mini\n"
    )
    File.write(swift_file, patched)
  end
end

ライブラリ側の Swift 実装は、おおむね下記の形になります。autoFocus + 距離指定で、13 mini でも安定して読めるようになりました。

private func configureFocus(for device: AVCaptureDevice) throws {
  try device.lockForConfiguration()
  defer { device.unlockForConfiguration() }

  if device.isFocusModeSupported(.autoFocus) {
    device.focusMode = .autoFocus
  }
  if device.isLockingFocusWithCustomLensPositionSupported {
    device.setFocusModeLocked(lensPosition: 0.4, completionHandler: nil)
  }
  if device.isAutoFocusRangeRestrictionSupported {
    device.autoFocusRangeRestriction = .near
  }
}

正規表現が引っかかる罠

最初に書いたパッチは、「修正前のコード」だけにマッチする正規表現でした。当然、pod install を 2 回走らせるとマッチしません。CI で壊れます(自分で仕込んだ罠に、自分で落ちました)。

修正後は「修正後のコードにも再マッチする」表現に変えました。これで何度 pod install しても冪等に動きます。地味ですけど、大事なところです。

ライト機能でも引っかかった

カメラの話のついでに。ライト機能(フラッシュ)でも別件で苦しみました。マルチカメラ対応デバイス(Pro 系の機種など)では、ライトを ON にするとスキャナが停止します。これも個別に対応しました。

実機を借りて確認する

最終的には iPhone 13 mini を友人から借りました。手元で挙動を確認できて、やっと安心しました。ありがたいです。リモートデバッグだけでは確証が持てません。デバイス依存の問題は、実機を一台確保するのが結局いちばん速いです。

まとめ

  • 機種固有の問題は、まずレビューと Crashlytics の機種情報を見る
  • ライブラリのパッチは Pods で自動化する。冪等性は正規表現で担保する
  • どうしても再現しないなら、実機を借りる

「全機種で動くアプリ」を作るのは大変です。持っていない端末のことは、持っている人に聞くしかない。次に同じことが起きたら、まっさきに友人へ連絡します。