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

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

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

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

Q&A

解決済

1回答

578閲覧

textViewにおいてボタンをタップしたらカーソルの左の文字を1字削除するコード

Tomzy

総合スコア104

Swift

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

0グッド

0クリップ

投稿2017/11/03 02:58

###やりたいこと
textViewにおいてボタンをタップしたらカーソルの左の文字を1字削除することです。

###やったこと
ボタンをタップしたらカーソルが1つ左に戻ることは下記のコードで成功しました。

@IBAction func arrow03(_ sender: Any) { // To one position to the left of the current cursor position // only if there is a currently selected range if let selectedRange = InputView.selectedTextRange { // and only if the new position is valid if let newPosition = InputView.position(from: selectedRange.start, offset: -1) { // set the new position InputView.selectedTextRange = InputView.textRange(from: newPosition, to: newPosition) print("ここまで来た05") } } }

そこで下記のコードで試しましたところ、後述のエラーがでて行き詰まりました。

@IBAction func Sakujo01(_ sender: Any) { // only if there is a currently selected range if let selectedRange = InputView.selectedTextRange { // and only if the new position is valid if let newPosition = InputView.position(from: selectedRange.start, offset: -1) { // ←削除ボタンが押された時の処理 InputView.selectedText = ""

出たエラー

Value of type 'UITextView' has no member 'selectedText'

エラーの場所
イメージ説明

###お願い

上記のエラーを解決してボタンをタップしたらカーソルの左の文字を1字削除する方法を教えてください。

###ご参考事項

  • 環境は下記のとおりです。

MacBook Pro (15-inch, 2016)
High Sierra OS10.13
Version 9.1 (9B55)Swift4にバージョンアップしました。

  • 下記のように定義しています。

@IBOutlet weak var InputView: UITextView!
@IBOutlet weak var Sentaku01: UIButton!

  • 画面は下記のとおりです。最初はキーボードはでておらず画面をタップして文字記入後削除ボタンを押して文字を消したいと思っております。

イメージ説明
イメージ説明

以上よろしくお願いします。

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

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

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

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

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

guest

回答1

0

ベストアンサー

エラーの原因はUITextViewにselectedTextという変数がないことです。UITextViewを覗いてみたところ確かにselectedTextは宣言されてませんでした(selectedRangeはあったんですが)。そこでコードを再度拝見したところ何らかのrangeは取得されているようなので、目先を少し変えて文字列の要らないところを除外しつつ二つに分割、その後再度結合するというやり方はいかがでしょうか。このやり方なら

textViewにおいてボタンをタップしたらカーソルの左の文字を1字削除する

という条件を満たせるかと存じます。

###サンプルコード

swift

1let alphabet = "abcdefghijklmn" 2let startIndex = alphabet.startIndex 3let fIndex = alphabet.index(startIndex, offsetBy: 5) 4let gIndex = alphabet.index(after: fIndex) 5let endIndex = alphabet.endIndex 6let firstStrings = alphabet[startIndex..<fIndex] 7let secondStrings = alphabet[gIndex..<endIndex] 8let dropF = firstStrings + secondStrings 9print(dropF). //abcdeghijklmn

###以下追記

ネットやUITextViewのコードを眺めてたらUITextViewのrangeやpositionって扱いにくいですねこれ。なのでもう一つ思いつきました。

swift

1@IBAction func deleteText(_ sender: UIButton) { 2 if let selectedRange = textView.selectedTextRange { 3 textView.replace(selectedRange, withText: "") 4 } 5}

これならどうでしょうか?

###追記の追記

swift

1@IBAction func deleteCharacterAtLeftOfCaret(_ sender: UIButton) { 2 3 //キャレットの位置 4     //文字列が"あいうえお"の時、キャレットが"あ"の左側にあるならばoffset == 0 5 let offset = textView.selectedRange.location 6      7     //キャレットが一番左にある場合削除する文字がないので抜ける 8 if offset <= 0 { 9 return 10 } 11 12     //テキストがない場合抜ける 13 guard let fullText = textView.text else { 14 return 15 } 16 17 let startIndex = fullText.startIndex 18 let endIndex = fullText.endIndex 19 20 //UITextView.index(_,offsetBy:)で得られるindexが負の数になると落ちる 21 let targetOfLeftIndex = fullText.index(startIndex, offsetBy: offset - 1) 22 let targetOfRightIndex = fullText.index(startIndex, offsetBy: offset) 23 24 let leftText = fullText[startIndex..<targetOfLeftIndex] 25 let rightText = fullText[targetOfRightIndex..<endIndex] 26 27 let modifiedText = leftText + rightText 28 29 textView.text = String(modifiedText) 30 31 }

投稿2017/11/03 03:55

編集2017/11/06 04:38
xAxis

総合スコア1349

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

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

Tomzy

2017/11/03 05:58

ありがとうございました。 私が狙っている削除をする文字列は1字の時もあるし100字の時もあります。頂いたコードをPlayGroundに下記入れて、いろいろ考えました。その結果offsetByを0にするとfirstStringsが""になり削除されることがわかりました。 以下のコードをどう私のアプリに折り込むかが問題ですが、これをベースに考えてみます。 let alphabet = "abcd" let startIndex = alphabet.startIndex let fIndex = alphabet.index(startIndex, offsetBy: 0) let firstStrings = alphabet[startIndex..<fIndex] let dropF = firstStrings print(dropF) //abcd
xAxis

2017/11/03 07:08 編集

定数alphabetは"abcd"でありこれは["a", "b", "c", "d"]の配列です。定数startIndexは0番目、定数fIndexも0番目になります。alphabet[startIndex..<fIndex]というのはalphabet[0..<0]、つまり空の配列[]が返ってるだけです。なのでこの場合print(dropF)は何も表示されません。 削除というニュアンスとは少々違うかもしれないです。
Tomzy

2017/11/03 07:09

早速下記のとおり実装してみました。 ビルドは成功し、あかさたな とキーボードでインプットし、←削除ボタンを押しても反応がありません。ここまで来た05 は記録されます。 //←削除を押した時⇒左の文字を削除 @IBAction func Sakujo01(_ sender: Any) { if let selectedRange = InputView.selectedTextRange { InputView.replace(selectedRange, withText: "") print("ここまで来た05") } }
Tomzy

2017/11/03 07:24

わかりました。あかさたな とインプットした上で文字 さた を選択した上で←削除ボタンをタップしたら、見事に さた 文字だけが削除されました。 このボタンは選択しないで単にカーソルの左を削除するものなのです(キーボードの削除ボタンと同じ機能)。よろしくお願いします。
Tomzy

2017/11/03 07:27

←選択 ボタンもまだ実装できていません。それが分かれば、そのコードを記載した上でこのコードで削除することができるわけですね。
xAxis

2017/11/03 08:04

>このボタンは選択しないで単にカーソルの左を削除するものなのです。 これに関しては自分の理解力の低さを改めて感じました。俺文章もっと読めよと。その理解力の低さにお付き合いさせてしまってすいませんでした。 ←選択ボタンに関してはselectedRangeとselectedTextRangeと現状あるコードをこねくり回せばなんとかなりそうなところまですでにいらっしゃるんじゃないかと思いますよ。
Tomzy

2017/11/03 08:08

成功しました。ありがとうございました。 下記のとおり実装しましたら、思った通りの機能が搭載できました。 キャレットが文字列の先頭に行かず削除したところにとどまっています。 あと、選択やコピーペーストが残っていますが、今回教わったコードをよく勉強してやってみます。ありがとうございました。 //←削除を押した時⇒左の文字を削除 @IBAction func Sakujo01(_ sender: Any) { if let fullText = InputView.text { let startIndex = fullText.startIndex let endIndex = fullText.endIndex let offset = InputView.selectedRange.location let targetOfLeftIndex = fullText.index(startIndex, offsetBy: offset - 1) let targetOfRightIndex = fullText.index(startIndex, offsetBy: offset) let leftText = fullText[startIndex..<targetOfLeftIndex] let rightText = fullText[targetOfRightIndex..<endIndex] let modifiedText = leftText + rightText InputView.text = String(modifiedText) } }
xAxis

2017/11/03 08:14

キャレットの位置が留まってるのは良かったです。arrow03関数内のコードの影響かもしれないですね。 私も新たな知見を得る事が出来ました。こちらこそありがとうございました。
Tomzy

2017/11/03 08:42

いやー、それにしても短時間に、これだけのコードを書いてしまうなんて凄いですね。勉強の意欲がでます。それにしても、今回の課題はアップルさんの標準の仕様(画面をタップして文字を選択するなど)から外れてボタンでいろいろな操作を行うものですからウェブサイト上にもサンプルが少なく本当に困りました。 私は年寄りのためのアプリを開発している82歳のクリエイターです。アップルさんには分からない年寄りの悩みを解決するためにやっています。ご参考までに、年寄りの指では小さな文字を選択したりカーソルを動かすことは難しいのです。今後ともご指導ください。
xAxis

2017/11/04 00:28

愚見ではありますが少数派の人々に対して関与していくというのは幅広く意義のある事だと感じています。ウチの母もiPhoneを使っていますが小さい文字を読む、テキストを操作するのは難しいと話していました。また父もiPhoneに興味を持ってはいるのですが母と同じくテキスト操作が難しいという話をしていました(PCでの操作も難しく感じているようです)。ですのでTomzyさんのアプリが完成したら是非とも両親に紹介したいです。 実は回答前にプロフィールを拝見してましてとてもクールなクリエイターの方だなと思っていました。微力ではありますがTomzyさんのお力になれたら嬉しく思います。
Tomzy

2017/11/04 02:58

ありがとうこざいます。 すでに年寄り用の スマホの勉強 をいくつかリリースしています。若い応援者のために設定を整理し年寄りのためには動画で操作方法を説明しています。どうぞご利用ください。 ところで教えていただいたカーソルの手前を削除するボタン操作ですが、カーソルが最初に戻った状態ではボタンを操作するとアプリが落ちます。その対策としてイフ文を使いカーソルが最初の場合は何にもしないと言うコードを考えています。
xAxis

2017/11/04 03:55 編集

「スマホの勉強」に興味を惹かれました。両親に伝えてみます。 コードに関してですが確かに落ちてしまいますね。 理由としてはUITextView.Index(_:offsetBy:)の二つ目の引数が負の値になるのがマズイようです。 なので対策としてはTomzyさんの考えてらっしゃるアプローチでいけると思います。 それと若干コードの可読性をあげてみました。
Tomzy

2017/11/05 02:09

まずは、自分でコードを考えようとしました。guareも使えました。 //←削除を押した時⇒左の文字を削除 @IBAction func Sakujo01(_ sender: Any) { let characters = String.characters guard InputView [characters.startIndex] = "" else { //以下は最初と一緒 if let fullText = InputView.text { let startIndex = fullText.startIndex let endIndex = fullText.endIndex let offset = InputView.selectedRange.location let targetOfLeftIndex = fullText.index(startIndex, offsetBy: offset - 1) let targetOfRightIndex = fullText.index(startIndex, offsetBy: offset) let leftText = fullText[startIndex..<targetOfLeftIndex] let rightText = fullText[targetOfRightIndex..<endIndex] let modifiedText = leftText + rightText InputView.text = String(modifiedText) }}}} しかしながら、下記のエラーがでてしまいました。 Instance member 'characters' cannot be used on type 'String' そこで、教えて頂いた let offset = textView.selectedRange.location      //テキストがない場合抜ける guard let fullText = textView.text else { return } を使って下記のコードにしました(FIXの指示がでてFIX実施結果)。ビルドも成功し、カーソルの左側は削除さます。しかし、最初にカーソルが位置する時に削除ボタンを押すと落ちて固まります。原因を考えているところです。 //←削除を押した時⇒左の文字を削除 @IBAction func Sakujo01(_ sender: Any) { _ = InputView.selectedRange.location //テキストがない場合抜ける guard (InputView.text) != nil else { return } if let fullText = InputView.text { let startIndex = fullText.startIndex let endIndex = fullText.endIndex let offset = InputView.selectedRange.location let targetOfLeftIndex = fullText.index(startIndex, offsetBy: offset - 1) let targetOfRightIndex = fullText.index(startIndex, offsetBy: offset) let leftText = fullText[startIndex..<targetOfLeftIndex] let rightText = fullText[targetOfRightIndex..<endIndex] let modifiedText = leftText + rightText InputView.text = String(modifiedText) } }
Tomzy

2017/11/05 03:02

下記のとおり修正しましたら狙ったとおりになりました。 ( guard (InputView.text) != nil を ="" に修正しました } //←削除を押した時⇒左の文字を削除 @IBAction func Sakujo01(_ sender: Any) { _ = InputView.selectedRange.location //テキストがない場合抜ける guard (InputView.text) != "" else { return } if let fullText = InputView.text { let startIndex = fullText.startIndex let endIndex = fullText.endIndex let offset = InputView.selectedRange.location let targetOfLeftIndex = fullText.index(startIndex, offsetBy: offset - 1) let targetOfRightIndex = fullText.index(startIndex, offsetBy: offset) let leftText = fullText[startIndex..<targetOfLeftIndex] let rightText = fullText[targetOfRightIndex..<endIndex] let modifiedText = leftText + rightText InputView.text = String(modifiedText) } }
xAxis

2017/11/05 04:33

そのコードだと自分の方だと落ちちゃいますね。 >イフ文を使いカーソルが最初の場合は何にもしないと言うコード というのを考えてらっしゃったようでしたので回答にはその辺りを記入していなかったのですがそのあたりも含めて回答修正します。
Tomzy

2017/11/05 07:53

ありがとうございました。 大分コードの意味が分かってきました。 結局下記のとおりにして上手く動きました。 これから選択やコピーペーストに挑戦します。 行き詰まったらまた質問します。 よろしくお願いします。 最終コード  ↓ ↓ ↓ //←削除を押した時⇒左の文字を削除 @IBAction func Sakujo01(_ sender: Any) { //キャレットの位置 //文字列が"あいうえお"の時、キャレットが"あ"の左側にあるならばoffset == 0 let offset = InputView.selectedRange.location //キャレットが一番左にある場合削除する文字がないので抜ける if offset <= 0 { return } //テキストがない場合抜ける guard let fullText = InputView.text else { return } let startIndex = fullText.startIndex let endIndex = fullText.endIndex //UITextView.index(_,offsetBy:)のoffsetByは負の数だと落ちる let targetOfLeftIndex = fullText.index(startIndex, offsetBy: offset - 1) let targetOfRightIndex = fullText.index(startIndex, offsetBy: offset) let leftText = fullText[startIndex..<targetOfLeftIndex] let rightText = fullText[targetOfRightIndex..<endIndex] let modifiedText = leftText + rightText InputView.text = String(modifiedText) }
xAxis

2017/11/06 00:53

無事動いてよかったです。選択やペーストの実装も上手くいくといいですね。お答えできる質問でしたらいつでも歓迎です。
Tomzy

2017/11/06 01:34

ありがとうございました。 カーソルの右を削除する問題にチャレンジして、少し前に動きました。一部機能するのですが不具合もでました。別途質問を掲載しますのでよろしくお願いします。
Tomzy

2017/11/08 03:06

削除後にカーソルの位置を削除した文字のところに持ってくるように、下記のコードを入れました。 //削除後の表示 InputView.text = String(modifiedText) //カーソルを削除した文字の後につける InputView.selectedRange.location = (offset-1)
Tomzy

2017/11/16 07:52

絵文字を削除するとアプリが落ちる不具合の解決 その後、この不具合を解決するためにxAxisさんの助言を受けて下記のコードで解決しました。 下記のコード内にアプローチについても整理しました。 ただ、絵文字を削除する場合2とか3回ボタンを押す必要があります。右の文字を削除する場合は一発でした。 コード  ↓ ↓ ↓ //←削除を押した時⇒左の文字を削除 /* アプローチについてカーソルの左文字削除について整理 絵文字を考慮しつつカーソルの左側を削除する場合、考えることは 1.入力された全ての文字列 2.startIndexからカーソルまでの文字列 3.削除する文字数 4.カーソルの左側に残った文字列の数 5.UTF16の文字列は「あ」という文字は1文字として認識されるが「????」という文字は2文字として認識される。さらに色付きの文字だったりすると倍になったりする(詳細はUTF16, unicode等検索して下さい) 6.5番を踏まえるとカーソルの左が普通の文字であろうと絵文字であろうと対応できるコードを書かなければならない 7. カーソルの左側を削除する場合もう一つ(というかこっちの方がはるかに楽な)アプローチがあります。それはUITextView.deleteBackward()を利用する方法です。こっちの方が楽に削除出来るかと。 1.はfullText 2.はfullText[startIndex..<cursorIndex] 3.はカーソルの左側1文字だけなのでamountOfCharacter = 1 4.は実機等画面に表示されている全ての文字列の数 - rightTextの数 - ammountOfCharacter で表します。 5.これのおかげでUITextView.index(_,offsetBy:)で隣の文字を取得するという方法が使えません。 6.なので4番で取得した文字列分fullTextから切り取る、という方法を取ります。 7.カーソルの左の1文字を削除する ボタンActionに」より⇒を作動させる InputView.deleteBackward() */ @IBAction func Sakujo01(_ sender: Any) { print("ここまで来た08b") //guard は条件を満たしていない時に、迅速にスコープを抜けるための処理(breakやreturn、例外のthrowなど)を行う。 //テキストがない場合抜ける guard let fullText = InputView.text else { return } print("ここまで来た08c") //カーソルの位置を定義 let offset = InputView.selectedRange.location //文字列が"あいうえお"の時、キャレットが"あ"の左側にあるならばoffset == 0 //キャレットが一番左にある場合削除する文字がないので抜ける if offset <= 0 { return } print("ここまで来た08d") //カーソルの左の文字列 let startIndex = fullText.utf16.startIndex let endIndex = fullText.utf16.endIndex //1文字のスペース let amountOfCharacter = 1 //カーソルの左の1文字を削除する InputView.deleteBackward() print("ここまで来た08e") //カーソルの左より1文字削除後の左の文字列 let targetOfLeftIndex = fullText.utf16.index(startIndex, offsetBy: offset - amountOfCharacter) //カーソルの右側の文字列 let targetOfRightIndex = fullText.utf16.index(startIndex, offsetBy: offset) //左の削除後の文字列定義 let leftText = fullText[startIndex..<targetOfLeftIndex] let rightText = fullText[targetOfRightIndex..<endIndex] let modifiedText = leftText + rightText //左の削除後の文字列の表示 InputView.text = String(modifiedText) //カーソルの位置表示 InputView.selectedRange.location = offset-amountOfCharacter }
xAxis

2017/11/16 11:08

左側の文字の削除は @IBAction func tap(_ sender: UIBarButtonItem) { textView.deleteBackward() } これでもいいです。
xAxis

2017/11/16 11:10

ちなみにdeleteForward()は無いので実装しました。今までやってきた事がdeleteForward()とでも言えるような内容です。まだ詰めなければなりませんが。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問