0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

iPhoneブラウザのバーコードスキャナが精度がわるくて専用アプリをつくった話②

0
Last updated at Posted at 2026-06-02

はじめに

前回は,Blazor WebAssembly でバーコードスキャナを動かしてみた話を書きました.

iPhone ではブラウザ経由の読み取り精度がどうしても厳しかったので,専用の iOS アプリを .NET MAUI で作ることにしました.
今回は,その続きとして Web アプリと iOS アプリ(MAUI)とのデータのやり取り をどう実装したのかをまとめます.

やりたいことは単純で,

  • Blazor 側でバーコード読み取りを開始する
  • iPhone の MAUI アプリを起動する
  • MAUI 側でバーコードを読む
  • 読み取った値を Blazor 側へ返す

という流れです.

単純にわたすだけではだめでした

iPhone ではアプリの切り替えが入りますので,Blazor 側の接続がそのまま維持されませんでした.
単純に「読めたらすぐ Blazor に push する」だけではうまくいきません.
あと,iPhoneアプリ側でクエリとかに読み込んだ文字列をつけてWebアプリのURLを叩くのものだめでした.新規タブで開いてしまうので,入力フォームに読み込んだ値を入れたいという要件ではうまくいきませんでした.
アプリ側でタブの制御みたいなものを考えましたが,できないようでした...

そこで今回は,SignalR とキャッシュを組み合わせて,一度サーバー側に預けてから,ブラウザ復帰後に取りに行く 形を取りました.

どういう構成にしたか

登場人物は以下の通りです.

  • Web アプリ
    • Blazor WebAssembly の画面です.
    • 画面ごとに Guid を発行して,自分の識別子にします.
  • BarcodeHub
    • Blazor 側と MAUI 側の間でメッセージを受け渡す SignalR の Hub です.
  • メッセージ管理クラス
    • 読み取ったバーコードを一時的に保存するクラスです.
  • MAUI アプリ
    • .NET MAUI で作った iPhone アプリです.
    • カメラでバーコードを読み取って,SignalR 経由で Web 側に送ります.

ざっくり言うと,

  • Blazor が自分用の Guid を作る
  • その Guid を付けて MAUI を起動する
  • MAUI がバーコードを読んでサーバーへ送る
  • ユーザーがブラウザへ戻る
  • Blazor が同じ Guid を使って値を取りに行く

という構成です.

全体の流れ

全体の流れは以下のようになります.

今回のポイントは,バーコードの値をすぐに画面へ渡さない ことです.
(色々試しましたが,直接渡すのは難しいと思います)
一度メッセージ管理クラスに保存しておいて,最終的には Blazor 側が GetMessage() で取りに行きます.

Blazor 側でやっていること

Blazor 側では,受信用のページがこのやり取りの入口になっています.

まず,画面ごとに Guid を 1 つ発行して,SignalR の接続先と Universal Link に載せています.

private Guid Id = Guid.NewGuid();

// SignalRサーバーのURL
private string HostUrl =>
    Navigation.ToAbsoluteUri("/hub".AddQueryParam(nameof(Id), GroupId.ToString())).ToString();

// iPhoneアプリ呼び出しURL
private string UniversalLink =>
    NativeCamera.CreateUniversalLink(
        (nameof(Id), Id.ToString()),
        (nameof(HostUrl), HostUrl));

Blazor は,最初から Id をクエリにつけた状態で SignalR に接続 します.

これで MAUI 側は,

  • 自分がどの画面向けの読み取りなのか
  • どの SignalR URL に接続すればいいのか

を受け取れるようになります.

ConnectionId ではなく Guid

最初は ConnectionId をそのまま MAUI 側に渡すことも考えました.
ただ,SignalR の ConnectionId は再接続時に変わる可能性があります.
(実際変わってしまって,ダメなケースが多かったです)

そのため,

  • Blazor を開いた時の接続
  • MAUI を使っている間に切れて,再接続した後の接続

ConnectionId が変わってしまうと,どの画面に返すべきか追えなくなります.

そこで,画面ごとに自前で Guid を発行して,それを Id として扱うようにしました.
この Guid は少なくとも「同じ画面セッションの間」は変わらないので,接続の張り直しが起きても追いやすいです.

SignalR サーバー

サーバー側では,起動処理,Hub,本文を一時保存する管理クラスが中心です.

起動処理では SignalR と MemoryCache を登録します.
重要なのは Hub の役割です.
Hub では,接続時にクエリから Id を取り出します.

private string Id => Context.GetHttpContext().Request.Query["Id"];

public void IntializeFromBlazor()
    => messageManager.Join(Id, Context.ConnectionId);

public void SendMessage(BarcodeMessage message)
    => messageManager.SendMessage(Id, message);

public BarcodeMessage GetMessage()
    => messageManager.GetStoredMessage(Id);

やっていることは以下の通りです.

  • Blazor が接続したら Id -> ConnectionId の対応を覚える
  • MAUI が送ってきたバーコードを Id をキーにして保存する
  • その Id の Blazor に "ReceivedMessage" を送る
  • Blazor 側は GetMessage() で中身を取りに来る

ここで大事なのは,データ本体を一回キャッシュしている ことです.

前述の通り,MAUI を開いている間に Blazor 側が受信できないケースがあるので,通知だけに頼ると取りこぼします.
そこで MemoryCache を使って,サーバー側に値を残すようにしています.

MAUI 側でやっていること

MAUI 側のアプリがバーコード読み取りをします.
機種のカメラをつかうので,読み取り精度が良いです.

やりたいことは単純で,読んだバーコードを Barcode にして SendMessage する ことです.

この Barcode は,例えば次のようなオブジェクトです.

public record struct Barcode(string Value, string Format);

Value はバーコードの値そのもので,Format は CODE128 や EAN13 のようなバーコード種類です.
今回は Web アプリ側でも バーコードの値と種類の両方 を使いたかったので,string ではなくオブジェクトにしています.

もし必要なのがバーコード文字列だけなら,ここは string で送ってもよいと思います.

Barcode = new(barcodeValue, barcodeFormat);
await SignalR.SendAsync("SendMessage", barcode);

ここでは Id を引数で渡していません.SignalRサーバーへの接続は起動にしてしまいます.
MAUI 側はユニバーサルリンクの URL に Id を含んだ HostUrl をもらっていて,それで SignalR 接続済み です.

iPhone ではブラウザに戻る操作が必要

バーコード送信後,MAUI 側は待機画面に遷移します.
ここでは次のような案内を表示するようにしました.

バーコードを送信しました.
画面左上のブラウザ名をタップするか
画面下部を右にフリックしてください

iPhone では,

  • MAUI がバーコードを送信する
  • ユーザーがブラウザへ戻る
  • Blazor 側が再開する

という手順になります.

ここは手動です.
自動的にブラウザに戻したかったですが,実現できませんでした.

ブラウザに戻ったあと

Blazor 側の受信ページでは,"ReceivedMessage" を受け取ったら GetMessage() を呼びます.

SignalRConnectionService.On("ReceivedMessage", async () =>
{
    var barcode = await SignalR.InvokeAsync<Barcode>("GetMessage");
    if (barcode != default)
        messages.Add($"{Id}: {barcode.Value}({barcode.Format})");
});

さらに,再接続後にも同じように GetMessage() を呼ぶようにしています.

このようにしているので,

  • 受信中にそのまま取れる場合
  • いったん切れてから戻ってきて取る場合

のどちらでも回収できるようになっています.

このあたりが,今回のデータ連携で一番やりたかったところです.

実装してみて

  • ConnectionId に依存しない
    • 再接続で変わる可能性があるためです.
  • Id を起点にする
    • Blazor と MAUI が同じ目印を共有できます.
  • データは一回キャッシュする
    • リアルタイム通知だけだと取りこぼすことがあるためです.
  • ブラウザの完全リロードには注意する
    • 画面を完全に作り直すと新しい Guid が発行されるためです.
  • iPhone 側での送信後の自動ブラウザ遷移は難しい.
    • できませんでした.

動いてる様子

※ この動画ではユニバーサルリンクは実装していません.実装すると起動時のダイアログが表示されなくなります.これはお試し実装ですので,URLスキームを使用しています.

動画プロジェクト 6.gif

まとめ

今回の実装は

Blazor が発行した Guid(Id) を使って,MAUI が読んだバーコードを SignalR サーバーへ預けて,ブラウザ復帰後に Blazor が取りに行く

という形です.

前回の記事では,ブラウザだけでどこまでできるかを試して,ブラウザでは厳しいことがわかりました.
今回はネイティブアプリに読み取りを任せて,どうにかして読み取りデータを Web アプリを連携させる形にしました.
実装は少し複雑になりましたが,iPhone のカメラを使えるので読み取り精度が上がって使い勝手は良くなりました.

Universal Link 周りはこちらに参考記事があるので,ぜひ見てください.

Azure BlobStorage 版
https://qiita.com/onigiripudding/items/9ee95b84335ea9794a0b

Azure Static Web App 版
https://zenn.dev/poipoionigiri/articles/d2b6b69ceed16d

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?