質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1681閲覧

UITextField内で空文字の時にバックスペースが入力されたか判断したい

Atsushi_Kygo

総合スコア7

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

1グッド

0クリップ

投稿2020/10/07 06:56

編集2020/10/07 07:09

前提・実現したいこと

現在Swiftの練習のためにGoogleKeepを参考にアプリを制作しています。
textFieldが空の時に、バックスペースが入力されたかを判定したいと考えています。
最終的には、空の時にバックスペースが入力されたらcellを削除するという機能にしたいです。

【完成イメージ】
バックスペースが押される前
イメージ説明

バックスペースが押された後(イメージ)
イメージ説明

発生している問題・エラーメッセージ

textFieldが空文字だとバックスペースを押しても何も起きない
(shouldChangeCharactersInが呼び出されない)

該当のソースコード

Swift

1// 2// ViewController.swift 3// GoogleKeepTest 4// 5// 6 7import UIKit 8import Firebase 9 10class ViewController: UIViewController { 11 12 @IBOutlet weak var tableView: UITableView! 13 14 var todoList:[String] = [] // TodoListを格納する配列 15 var currentCellIndex = 0 // 現在のセルの添字を代入する 16 17 18 // MARK: View Did Load 19 override func viewDidLoad() { 20 super.viewDidLoad() 21 22 setTableView() // TableViewのdelegateなどの設定を行う 23 } 24 25 26 /// Firestoreにlistを保存する 27 func saveItemData() { 28 Firestore.firestore().collection("TodoCollection").document().updateData(["todoList": todoList]) { (error) in 29 if error != nil { 30 print("\n\nerror内容: (error as Any)") 31 } else { 32 print("## データの保存に成功") 33 print("list: (self.todoList)") 34 self.tableView.reloadData() 35 } 36 } 37 } 38 39} 40 41 42 43 44 45 46// MARK: ProtocolExtension 47extension ViewController: UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate { 48 49 50 51 // MARK: TableView 52 53 /// tableViewのdelegateなどの設定をする 54 func setTableView() { 55 tableView.delegate = self 56 tableView.dataSource = self 57 tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "cell") // 入力欄用のカスタムセル 58 tableView.register(UINib(nibName: "AddCustomCell", bundle: nil), forCellReuseIdentifier: "AddCell") // 追加ボタン用のカスタムセル 59 } 60 61 62 /// アイテムの数を返す 63 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 64 return self.todoList.count + 1 // listの要素数 + 追加ボタン用のカスタムセル 65 } 66 67 68 /// セルが選択された時 69 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 70 // 追加ボタンが押された場合 71 if indexPath.row == todoList.count { 72 // listに空の要素を追加する 73 currentCellIndex = todoList.count 74 todoList.append("") 75 saveItemData() 76 } 77 78 // セルがタップされても背景を白に戻す 79 tableView.deselectRow(at: indexPath, animated: true) 80 } 81 82 83 /// セルの高さを返す 84 func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 85 return CGFloat(40) 86 } 87 88 89 /// cellを生成して返す 90 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 91 92 // 通常の場合(入力用のセル) 93 let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomCell 94 cell.todoLbl.text = todoList[indexPath.row] 95 cell.todoLbl.delegate = self 96 cell.todoLbl.tag = indexPath.row 97 98 // cellの数がlistの要素数に達したら 99 if indexPath.row == todoList.count { 100 // 追加ボタン用のセルを生成する(画像だと「+ リストアイテム」と書いてあるもの。 セルじゃなくてもよいかも?) 101 let addCell = tableView.dequeueReusableCell(withIdentifier: "AddCell", for: indexPath) 102 return addCell 103 } 104 105 // 入力中 or 追加されたセルにフォーカス 106 if currentCellIndex == indexPath.row { 107 cell.todoLbl.becomeFirstResponder() // フォーカスする 108 } 109 110 return cell 111 } 112 113 114 115 116 117 // MARK: UITextField 118 119 120 /// returnが押された時 121 func textFieldShouldReturn(_ textField: UITextField) -> Bool { 122 currentCellIndex = textField.tag + 1    // 入力中セルの添字に+1する 123 todoList.insert("", at: currentCellIndex) // 空の要素を追加する 124 saveItemData() // Firestoreに保存 125 126 return true 127 } 128 129 130 131 /// 1文字入力されるたびに呼ばれる 132 func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { 133 134 // MARK: ここで、バックスペースが押された時にセルを削除する機能を作成しようとした(空文字の時はこのメソッドが呼び出されない) 135 print("\n\n\n=========== Should Change Char ==========") 136 print("list: (todoList)") // listを表示 137 currentCellIndex = textField.tag // 入力中の添字を代入 138 139 140 if let char = string.cString(using: String.Encoding.utf8) { 141 let isBackSpace = strcmp(char, "\b") 142 143 // 入力された文字がバックスペースか否か 144 if (isBackSpace == -92) { 145 print("\\ バックスペースが入力された") 146 // 末尾の1文字を削除 147 todoList[currentCellIndex] = String(todoList[currentCellIndex].dropLast()) 148 } else { 149 print("\\ 文字が入力された: (string) ") 150 todoList[currentCellIndex] = textField.text! + string 151 } 152 } 153 154 saveItemData() 155 currentCellIndex = textField.tag 156 return true 157 } 158 159} 160 161

試したこと

  1. ゼロ幅スペースをセルに初期値として置く

-> やり方がわからなかった
2. 全てのセルに初期値としてスペースを入れておく(スペースが削除されたらセルを削除する方式)
-> 一瞬だけスペースが削除されるアニメーションが起きるため適切ではないと考えた
3. 入力された文字を取得して、バックスペースの時に処理をする方法を調べた
-> よくわからなかった

補足情報(FW/ツールのバージョンなど)

Swift 5.3
Xcode 12.0.1

TsukubaDepot👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

はてなブログで下記のような記事を発見しました。

これがそのまま使えるのではないでしょうか。

以下、検証用のコードです。

Swift

1import UIKit 2 3class ViewController: UIViewController { 4 @IBOutlet weak var textField: CustomUITextField! 5 6 override func viewDidLoad() { 7 super.viewDidLoad() 8 // Do any additional setup after loading the view. 9 // textField.delegate とは違うので注意 10 textField.deletionDelegate = self 11 } 12} 13 14extension ViewController: CustomUITextFieldDelegate { 15 func didDeleteBackward(_ textField: CustomUITextField) { 16 if let text = textField.text { 17 if text.isEmpty { 18 print("文字がない状態で delete が押されました") 19 } else { 20 print("文字を削除しました。") 21 } 22 } 23 } 24}

Swift

1// Cited from https://culumn.hatenablog.com/entry/2018/02/23/012659 2 3import UIKit 4 5protocol CustomUITextFieldDelegate: class { 6 func didDeleteBackward(_ textField: CustomUITextField) 7} 8 9class CustomUITextField: UITextField { 10 weak var deletionDelegate: CustomUITextFieldDelegate? 11 12 override func deleteBackward() { 13 super.deleteBackward() 14 deletionDelegate?.didDeleteBackward(self) 15 } 16}

###2020年10月9日追記

あまり良い案ではありませんが、こういう方法が一つかもしれません。
充分にテストしたわけではないので、まだ抜けがあるかもしれません。

Swift

1class CustomUITextField: UITextField { 2 weak var deletionDelegate: CustomUITextFieldDelegate? 3 4 override func deleteBackward() { 5 // 実際に削除する直前の文字数を記録しておく 6 let beforeWordsCount = self.text?.count ?? 0 7 super.deleteBackward() 8 deletionDelegate?.didDeleteBackward(self, previousCount: beforeWordsCount) 9 } 10}

Swift

1extension ViewController: CustomUITextFieldDelegate { 2 func didDeleteBackward(_ textField: CustomUITextField, previousCount prev: Int) { 3 if prev == 0 { 4 print("文字がない状態で delete が押されました") 5 } else { 6 print("文字を削除しました。") 7 } 8 } 9}

投稿2020/10/08 00:32

編集2020/10/08 13:06
TsukubaDepot

総合スコア5086

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Atsushi_Kygo

2020/10/08 12:51

回答ありがとうございます!! 非常に参考になる回答と記事でありがたいです。 機能を追加したところ、textFieldの文字が削除された後にdidDeleteBackwardが呼び出されるのですが、これはデフォルトなのでしょうか?(カスタムセルでファイルが分かれているので、うまくいってない可能性もあります) 確かに空の状態でもdeleteの判定は出来て、セルも削除できるのですが、残り1文字を削除すると textFieldが空になる => didDeleteBackwardが呼ばれる => textField.isEmptyがtrueになる => セルを削除 となってしまいます。
TsukubaDepot

2020/10/08 13:07

そこまで十分には検証していませんでした(残り文字数まで考えていませんでした)。 一つの方法としては、削除直前の文字数を返すように delegate の仕様を変更する方法かと思いますが、これも十分に試験する必要はあるかと思います。
Atsushi_Kygo

2020/10/08 14:36

なるほど、なんとなくイメージは掴んだので試してみます! ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問