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?

Flutter × iOS(Swift)ネイティブ連携で「SearchBar だけ maxByte 制限が効かない」を直した話

Posted at

背景

  • プロジェクトには共通部品がありました。
    • TextField(単行入力)
    • TextArea(複数行入力)
    • SearchBar(検索バー、iOS ネイティブ UITextField のラッパー)
  • TextFieldTextArea は、Dart 側で maxBytes を指定すると「全角=2/半角=1」のバイト換算で文字数制限が効くのに、SearchBar は制限が効かない状態でした。

結論(原因と対策)

  • 原因: SearchBar の iOS プラグイン(Swift)に「Dart 側で正規化(トリム)した値をネイティブ UI に反映する処理」が実装されていなかった。
  • 対策: Swift のプラグインに「update 受信時に UITextField.text を更新する処理(テキスト反映)」を追加したことで、Dart 側の maxBytes 制御が UI にも反映されるようになった。

なぜそうなるのか

  • 通常の入力は、ユーザーが UITextField に直接タイプするため、ネイティブ UI は自動で最新になります(Dart 側は通知されるだけ)。
  • しかし、バイト制限を Dart 側で行う場合、入力超過時に Dart で文字列をトリムします。つまり「正しい文字列」は Dart 側にあり、ネイティブの UITextField は“古いまま”になります。
  • そのため、Dart 側で正規化した文字列を、ネイティブ UI に「書き戻す」必要があります。これが SearchBar の Swift 側に欠けていた部分でした(TextField/TextArea はすでに実装済み)。

実装(サンプルコード)

1) Dart 側(SearchBar VM 側)での入力時バイト制限と巻き戻し

  • 入力されるたびにバイト長を判定し、超過時は先頭から maxBytes 分だけを残す。
  • その後、ネイティブ UI にも即時反映(巻き戻し)する。
/// サンプル: SearchBar VM 側
void _onNativeInput(String raw, int maxBytes, String ident) {
  var next = raw;
  if (getBytes(next) > maxBytes) {
    next = left(argTargetStr: next, argBytes: maxBytes); // 全角2/半角1換算でトリム
    updateTextOnNative(text: next, ident: ident);        // ネイティブUIへ巻き戻し
  }

  if (_previousValue == next) return; // ループ防止
  _previousValue = next;
  state = state.copyWith(text: next);
  onChangedValue(next);
}

/// サンプル: ネイティブへ反映
Future<void> updateTextOnNative({required String text, required String ident}) async {
  final channel = MethodChannel('native_search_bar#$ident');
  await channel.invokeMethod('update', {'text': text});
}

補足:

  • getBytes/left は「全角=2/半角=1」でのバイト換算・先頭切り出しのユーティリティ(実装は任意)。
  • _previousValue で前回値を保持し、不要な再通知を避ける。

2) iOS(Swift)側プラグインでの「テキスト反映」実装

  • Dart → iOS の update が来たら、UITextField.text を更新する。
  • 空文字は既存のクリア相当の処理に委譲。
  • 不要なレイアウト振れを避けるなら、キャレット位置維持(オプション)。
// サンプル: SearchBar の Swift プラグイン
_channel.setMethodCallHandler { [weak self] call, result in
  guard let self = self else { return }
  switch call.method {
  case "update":
    if let args = call.arguments as? [String: Any?] {
      let newText = (args["text"] as? String) ?? ""

      if newText.isEmpty {
        _ = self.textFieldShouldClear(self._textField) // クリア相当
      } else {
        // (任意)フォーカス中のキャレット位置を保持
        let wasEditing = self._textField.isFirstResponder
        var previousOffset: Int?
        if let range = self._textField.selectedTextRange {
          previousOffset = self._textField.offset(from: self._textField.beginningOfDocument, to: range.start)
        }

        // テキスト反映
        self._textField.text = newText

        // (任意)キャレット復元(短くなったら末尾へクランプ)
        if wasEditing, let prev = previousOffset {
          let clamped = max(0, min(newText.count, prev))
          if let pos = self._textField.position(from: self._textField.beginningOfDocument, offset: clamped),
             let textRange = self._textField.textRange(from: pos, to: pos) {
            self._textField.selectedTextRange = textRange
          }
        }
      }
      result(nil)
    } else {
      result(FlutterError(code: "invalid_args", message: "update: invalid arguments", details: nil))
    }

  default:
    result(FlutterMethodNotImplemented)
  }
}

ポイント:

  • 「通常のタイピング」ではネイティブが UI 更新しますが、「Dart 側でトリムした文字列」はこの update でネイティブに書き戻さないと UI に反映されません。
  • result(nil) を返すことで MethodChannel 呼び出しを正しく完了。
  • ループを避けるため、ここで input を送り返す必要はありません(送り返す場合は Dart の差分ガードが必須)。

まとめ

  • 問題: SearchBar だけ maxBytes 制限が効かなかった。Dart でトリムしても UI が更新されない。
  • 原因: iOS プラグイン(Swift)に「Dart→ネイティブのテキスト反映」がなかった(TextField/TextArea は実装済み)。
  • 対策:
    • Dart 側: 入力都度バイト制限→必要時に update でネイティブへ巻き戻し
    • Swift 側: update 受信で UITextField.text を更新(任意でキャレット維持)
  • 結果: SearchBar でも TextField/TextArea と同様に maxBytes 制限が正しく機能するようになった。

この方針は「ネイティブ側で事前ブロック(全角=2/半角=1のバイト判定)する」実装でも代替可能ですが、既存の TextField/TextArea と同じ「Dart 側で制御+ネイティブに書き戻す」設計に寄せると統一性が高く、移植/保守も容易です。

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?