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

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

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

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

Q&A

解決済

1回答

319閲覧

utf16において1キャラクターの現し方(ボタンによる絵文字の削除方法)

Tomzy

総合スコア104

Swift

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

0グッド

0クリップ

投稿2017/11/17 12:03

###やりたいこと
カーソルの左の文字をボタンで削除する場合1回のタップで絵文字を削除したい。

###問題点
コーディングをして半角英数字、全角日本文字は削除できますが、絵文字を削除する場合、文字により2回か3回タップする必要が発生しています。

###考察と質問
下記のコードにおいて
//1文字のスペース
let amountOfCharacter = 1
の右辺の1をutf16で表現できれば解決すると思いました。
解決策を教えてください。

###コード

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文字を削除するこのコードをCommentOutしても同じ結果になった // InputView.deleteBackward() print("ここまで来た08e") //カーソルの左より1文字削除後の左の文字列の順位 let targetOfLeftIndex = fullText.utf16.index(startIndex, offsetBy: offset - amountOfCharacter) //数字の1を入れたら同じ現象になった     //let targetOfLeftIndex = fullText.utf16.index(startIndex, offsetBy: offset - 1) //カーソルの右側の文字列の順位 let targetOfRightIndex = fullText.utf16.index(startIndex, offsetBy: offset) //右側の文字列 let rightText = fullText[targetOfRightIndex..<endIndex] //左の削除後の文字列定義(文字列の順位で設定) //let remain = fullText.underestimatedCount - String(rightText).underestimatedCount - amountOfCharacter //左側の文字列 //let leftText = fullText.suffix(remain) let leftText = fullText[startIndex..<targetOfLeftIndex] //右側の文字列 //let rightText = fullText[targetOfRightIndex..<endIndex] //左右の文字列を並べた全文字列 let modifiedText = leftText + rightText //左の削除後の全文字列の表示 InputView.text = String(modifiedText) //カーソルの位置表示 InputView.selectedRange.location = offset-amountOfCharacter } ```、 ###ご参考事項 - 環境は下記のとおりです。 MacBook Pro (15-inch, 2016) High Sierra OS10.13 Version 9.1 (9B55)Swift4にバージョンアップしました。 - 下記のように定義しています。 @IBOutlet weak var InputView: UITextView! @IBOutlet weak var Sakujo01: UIButton! - 画面は下記のとおりです。最初はキーボードはでておらず画面をタップして文字記入後削除ボタンを押して文字を消したいと思っております。画面のカーソルの左は削除後Aが現れもう一度タップすると消えます。 ![イメージ説明](edf06f9422717dd3e25ec43c5bf35ec9.png) よろしくお願いします。

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

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

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

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

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

guest

回答1

0

ベストアンサー

いつだったかのコメント欄にすでに書いていますが、左側だけならシンプルに

swift

1@IBAction func deleteLeftCharacter(sender: UIButton) { 2 InputView.deleteBackward() 3}

とやる選択肢もあります。ちなみにInputView.deleteForward()は無いので実装の必要がありました。

一からコードを書くならば、やることを反転させて

swift

1@IBAction func deleteLeftCharacter(_ sender: UIBarButtonItem) { 2 3 guard let fullText = textView.text else { 4 return 5 } 6 7 let location = textView.selectedRange.location 8 let count = fullText.utf16.count 9 if location <= 0 { 10 return 11 } 12 let offset = location - count 13 14 let endIndex = fullText.utf16.endIndex 15 16 let cursorIndex = fullText.utf16.index(endIndex, offsetBy: offset) 17 let rightText = fullText[cursorIndex..<endIndex] 18 19 let amountOfFullTextCharacter = fullText.underestimatedCount 20 let amountOfRightTextCharacter = String(rightText).underestimatedCount 21 let amountOfDeleteCharacter = 1 22 let remain = amountOfFullTextCharacter - amountOfRightTextCharacter - amountOfDeleteCharacter 23 24 let leftText = fullText.prefix(remain) 25 26 let modifiedText = leftText + rightText 27 28 textView.text = String(modifiedText) 29 30 textView.selectedRange.location = leftText.utf16.count 31 32 }

こうなります。

投稿2017/11/17 13:57

編集2017/11/17 13:57
xAxis

総合スコア1349

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

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

Tomzy

2017/11/17 14:44

ありがとうございます。勘違いをしていました。 簡単な方を試しました。下記のコードです。絵文字も完璧に削除できました。 複雑な方も勉強のために明日試してみます。 @IBAction func Sakujo01(_ sender: Any) { print("ここまで来た08b") //guard は条件を満たしていない時に、迅速にスコープを抜けるための処理(breakやreturn、例外のthrowなど)を行う。 //テキストがない場合抜ける //fullTextのところは修正を要するみたいです。⇒黄色マークがでています。 guard let fullText = InputView.text else { return } print("ここまで来た08c") //カーソルの位置を定義 let offset = InputView.selectedRange.location //文字列が"あいうえお"の時、キャレットが"あ"の左側にあるならばoffset == 0 //キャレットが一番左にある場合削除する文字がないので抜ける if offset <= 0 { return } print("ここまで来た08d") // //カーソルの左の1文字を削除する InputView.deleteBackward() // print("ここまで来た08e") } //
xAxis

2017/11/17 14:57

機能してよかったです。 InputView.deleteBackward()は上記に書いた通り単体でいい塩梅に削除してくれてるようなのでguard文とかif let文で早めにreturnしたりしなくても大丈夫じゃないかなとか思います。
xAxis

2017/11/17 14:59

なので @IBAction func Sakujo01(_ sender: Any) { InputView.deleteBackward() } こうですかね。
Tomzy

2017/11/18 00:36

こんにちは。そうでした。if文もguard文もoffsetの定義もCommentOutしましたが機能します。半角英数字、全角日本文字、各種絵文字も削除できました。ありがとうございました。 これから複雑な方もやってみます。
xAxis

2017/11/18 08:56

了解です。また質問がありましたらどうぞ。
Tomzy

2017/11/19 05:13

末尾のとおりコードを書きました。第2案でもカーソルの左を削除できました。半角英数字、全角日本文字絵文字も削除できました。 質問: カーソルを1文字進めたり遅らせたりするのに絵文字が入ると2〜3度押す必要があります。欲がでて修正を試みても実現しません。教えてください。 対象のコードは下記のとおりです。  ↓ ↓ ↓ //カーソル03を押した時⇒一字カーソルが戻る @IBAction func arrow03(_ sender: Any) { // To one position to the left of the current cursor position //1文字のスペース let amountOfCharacter = 1 // 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: -amountOfCharacter) { // set the new position InputView.selectedTextRange = InputView.textRange(from: newPosition, to: newPosition) print("ここまで来た05") } } } ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー カーソル左の文字を1字削除するコード⇒成功  ↓ ↓ ↓ @IBAction func Sakujo01(_ sender: Any) { print("ここまで来た08b") // 第1案カーソルの左の1文字を削除する //InputView.deleteBackward() // 第2案カーソルの左の1文字を削除する //全くテキストがない場合抜ける、fullTextの定義 guard let fullText = InputView.text else { return} //locationと絵文字が混じった場合のcountの定義 let location = InputView.selectedRange.location let count = fullText.utf16.count //カーソルの左にテキストがない場合抜ける if location <= 0 { return } print("ここまで来た08c") //カーソルの位置を定義 let offset = location - count //絵文字が含まれた場合の最後の位置定義 let endIndex = fullText.utf16.endIndex //カーソル位置の定義 let cursorIndex = fullText.utf16.index(endIndex, offsetBy: offset) //カーソル右の文字列 let rightText = fullText[cursorIndex..<endIndex] //全体の文字数 let amountofFullTextCharacter = fullText.underestimatedCount //カーソル右の文字列文字数 let amountofRightTextCharacter = String(rightText).underestimatedCount //削除された文字数 let amountofDeletCharacter = 1 //削除後のカーソル左の文字 let remain = amountofFullTextCharacter - amountofRightTextCharacter - amountofDeletCharacter //削除後のカーソル左の文字列 let leftText = fullText.prefix(remain) //削除後のカーソル左右の文字列 let modifiedText = leftText + rightText //削除後のカーソル左右の文字列表示 InputView.text = String(modifiedText) //削除後のカーソル位置表示 InputView.selectedRange.location = leftText.utf16.count print("ここまで来た08d") }
xAxis

2017/11/19 06:55

let newPosition = InputView.position(from: selectedRange.start, offset: -amountOfCharacter) これを let newPosition = textView.position(from: selectedRange.start, in: .left, offset: amountOfCharacter) こうしてみてはいかがでしょう?
Tomzy

2017/11/19 07:57

早速ありがとうございました。 下記のコードでカーソルの左右への移動が絵文字にも有効になりました。 なお、最近出版されたSwift4iPhoneアプリ開発入門ノートChapter4 String でIndexの使い方など読みまして理解を深めました。 コード  ↓ ↓ ↓ //カーソル03を押した時⇒一字カーソルが戻る @IBAction func arrow03(_ sender: Any) { // カーソルの現在位置より1字左に移動 絵文字でも有効 //1文字のスペース let amountOfCharacter = 1 // 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, in: .left, offset: amountOfCharacter) { // set the new position InputView.selectedTextRange = InputView.textRange(from: newPosition, to: newPosition) print("ここまで来た05") } } } //カーソル04を押した時⇒一字カーソルが進む @IBAction func arrow04(_ sender: Any) { //1文字のスペース let amountOfCharacter = 1 // 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, in: .right, offset: amountOfCharacter) { // set the new position InputView.selectedTextRange = InputView.textRange(from: newPosition, to: newPosition) print("ここまで来た06") } } }
xAxis

2017/11/19 09:36 編集

レビューまでは出来ていないのですがコードが機能してよかったです。 学習が進んでいくのは素晴らしいことだと思います。また学習段階でコメントアウトをきっちりされてるのも良いですし(本当に素晴らしいことだと思いますよ)、カーソルの移動を左右両方実装出来たのは学習された事をご自身で発展させたいい証明になったのではないでしょうか。素晴らしいです。 さて、もう一方のコメントでも触れておりましたがあちらで出たwarningは定数の命名を少々変えていたなら避けられたと思っています。 具体的には "let selectedRange = InputView.selectedTextRange" を "let selectedTextRange = InputView.selectedTextRange" とすることです。 これだけで何が変わるのか、と思われるかもしれませんがコードを読むときに意識が変わります。 既にご存知の通り一連の関数の実装にはたくさんのプロパティやメソッドが出てきました。selectedTextRange, selectedRange, textRange, さらにはpositionなどなどありました。これらを扱って実装していったわけですが、一度にたくさんの物を意識するのはかなり苦しかったのではないでしょうか。 どんなプログラマでもコードを読んでいるときにまとめて意識できる量にはやはり限界があります。そこでまとめて意識できる量を減らそう、読みやすくしようするために使われるのが命名規則というものです。 良い具体例が既に一つあります。 let newPosition = InputView.position(from: selectedRange.start, in: .right, offset: amountOfCharacter) これなんかとっても分かりやすいです。このコード1行だけで新しいポジションが生成されたと言うことだけでなくそのポジションを扱っていくという方向性も分かります。 逆にとっても分かりにくい命名の仕方というのもあります。手前味噌ではありますが私の答えた回答で命名が非常に分かりにくくなったものがあります。 https://teratail.com/questions/100469 こちらでされてた方の質問の背景等から命名に関してはどうにもならなかったのですが、関数名や定数名からではコードの流れが自分自身もう既によく分からない始末です(やってる操作を読んでいけば分かるのですが)。もしよろしければ関数やenumの中身は置いといて命名だけでも見てみてください。きっと訳が分からないと思います。そして名前の重要性と言うのを少しでも感じてもらえれば嬉しいです。 命名規則に関して興味が湧かれたなら「リーダブルコード」と言う本がありますのでそちらもお勧めしておきます。
Tomzy

2017/11/19 09:49

本当に貴重な助言ありがとうございます。大変励みになります。 実は10月26日に復興庁主催のシニアプログラミングネットワーク関係のイベントがりシニアの開発者として講演をしました。次回は広島で12月1日と2日にあります。講演内容にteratailでのこういう助言を紹介したいと思っています。差し支えなければお名前を頂けますか。 カーソルの左側の選択は下記のコードで上手くいきました。しかし、まだ右側では苦戦をしています。 コード  ↓ ↓ ↓ //←選択を押した時⇒左側を選択する @IBAction func sentaku02(_ sender: Any) { // カーソルの現在位置より1字左に移動し選択 絵文字でも有効 // only if there is a currently selected range if let selectedRange = InputView.selectedTextRange { //削除前のカーソルのポジション guard let currentPosition = InputView.position(from: selectedRange.end, in: .left, offset: 0), //削除後のカーソルポジション let leftPosition = InputView.position(from: selectedRange.start, in: .left, offset: 1) else { return } //選択範囲を表示 InputView.selectedTextRange = InputView.textRange(from: currentPosition, to: leftPosition) } } // // ↓ 選択を押した時カーソルの右を選択する @IBAction func sentaku03(_ sender: Any) { //選択範囲の定義 if let selectedRange = InputView.selectedTextRange { //削除前のカーソルのポジション guard let currentPosition = InputView.position(from: selectedRange.end, in: .right, offset: 0), //削除後のカーソルポジション let rightPosition = InputView.position(from: selectedRange.start, in: .right, offset: 1) else { return } InputView.selectedTextRange = InputView.textRange(from: currentPosition, to: rightPosition) } }
xAxis

2017/11/20 02:02 編集

おはようございます。 >復興庁主催のシニアプログラミングネットワーク関係のイベント なんだか楽しそうなイベントですね!そんなところに登壇されるなんてクールですね!名前はX軸やY軸のX軸から来てるのでエックスアクシズなどとお呼びいただければ。 確かに左側を選択はこれで良さそうですけど右側がちょっと思ったような挙動じゃないですね。 textView.selectedTextRangeそのものと.startと.endが一体何を示すのか見てみましょう。 あいうえ|おかき|くけこ 上の略図は文字「お」から文字「き」までを選択されている状態を表しています。この時のUITextView.selectedTextRangeが「|おかき|」.startが「お」の左側、.endが「き」の右側を指しています。 んでここで正しい挙動をしている左側の選択をちょっと掘り下げてみます。positionの生成はもう既に正しいので掘り下げるのは最後の行の.textRange(from: currentPosition, to: leftPosition)です。これをswiftではなく可能な限り日本語で表現するならば、 selectedTextRange.endから左方向へoffset0だけ移動したpotisionから selectedTextRange.startから左方向へoffset1だけ移動した範囲を textRangeの値とする くらいには表現出来ます。 じゃあこれを上の略図に実行しようとすると「き」の右側は変化なしです。が、「お」左側は左方向へoffset1だけ移動するので あいう|えおかき|くけこ こうなりますね。 ここで本題の右側ですが、同様の略図を用意すると あいうえお|か|きくけこ この時の.startは「か」の左側です。そして.endは「か」の右側。この状態でsentaku03関数を適用すると。。。? コードから上手いこと反転させようとしているのは分かります。なのであと一歩で完成ですよ!
Tomzy

2017/11/24 08:39

ご親切にありがとうございました。 暫く他の課題をやっていました。近況をご報告します。 シニアプログラミングネットワークのもくもく会で教わりまして、記号の説明に画像を使いタップするたびに画像が変わるのは成功。コピーペーストも成功。今日は先ほどからAdMov実装に成功。ただ、画面の大きさによって位置を調整する必要がでたのでこれから勉強です。 これから、教えて頂いたカーソルの右側選択に取りかかります。頂いた文章をスクリーンショットして印刷をしました。じっくり眺めながらコーディングにとりかかります。 削除ボタンがカーソルの左右を削除はできていますが、if文で文字選択がされている場合は選択部分も削除するを実装したいと思います。 残る課題は画面にMail,メモ、LINE、Google検索を起動して残る文章全部を選択してそれぞれのアプリに転写する機能です。 それに当初は作った原稿をテーブルビューに転写する構想でしたが複雑ですし、文章を残すのは下手をすると大切なお客様の文章が消えてしまうリスクが残るので取りやめて、原稿1、原稿2,原稿3の画面したいと思っています。タブ4のゲームは既にコードがありますし、タブ5の動画説明はウェブサイトに動画を載せるだけなので何度もやっています。来週申請目標で頑張ります。
Tomzy

2017/11/24 09:53

できました。下記のようなコードにしました。途中エラーがでたり、選択がひと文字だったり何度もやりなおして、たどりつきました。 情けなくなります。 コード  ↓ ↓ ↓ // ↓ 選択を押した時カーソルの右を選択する @IBAction func sentaku03(_ sender: Any) { //選択範囲の定義 if let selectedRange = InputView.selectedTextRange { //削除前のカーソルのポジション⇒selectedTextRange.startから左方向へoffset0だけ移動したpositionから guard let currentPosition = InputView.position(from: selectedRange.start, in: .left, offset: 0), //削除後のカーソルポジション⇒selectedTextRange.endから右方向へoffset1だけ移動した範囲をtextRangeの値とする let rightPosition = InputView.position(from: selectedRange.end, in: .right, offset: 1) else { return } // // InputView.selectedTextRange = InputView.textRange(from: currentPosition, to: rightPosition) } }
xAxis

2017/11/25 11:57

こんばんは、お返事遅くなってしまいすいません。 少々体調を崩してました。今は実家から送られてきたみかんを頬張りながらコメントしています。 そしてご報告していただきありがとうございます。どんどん機能が追加されていく様をみられるのはとても嬉しいです。かなり精力的に学習なさっているのですね。AdMovまで実装されるとはすごいです。 >画面の大きさによって位置を調整する必要がでたのでこれから勉強です。 これはautolayoutで処理してく感じですか?autolayoutはそれだけで一冊本が出てるくらい色々ある&制約をかけていく順番で意味合いが変わったりする場合があるので焦らず進めてみてください。自分も制約のかけ方を間違えてやり直しとかありますw 来週申請上手くいくといいですね。陰ながら応援しております。
xAxis

2017/11/25 12:04

コードの方ですが読むかぎりでは機能してそうですね。 >情けなくなります。 若輩者がこのような事を申し上げるのは気が引けますが誤解を恐れずに申し上げますと、私がコードを直接書くでもなく試行錯誤の結果Tomzyさん自らコードを仕上げられたのですから誇っていい事だと思います。それに加えて最初にお答えした時よりも理解もされていると感じています。この調子ですよ!
Tomzy

2017/11/26 00:56

コメントありがとうございます。励みになります。その後のご報告します。以前「もどる」ボタンを考えていましたが、Undo is astonishingly hard. という記述があるのを見つけましたので断念しました。また、現在Mail、メモ、LINE、Google検索のアプリを起動する機能も難しいのでひとつくらいに絞りバージョンアップの時に増やそうかと思っています。また、広告を入れるとスペースが狭すぎるのでボタンの個数を一行7個に増やして2列にしようかと思っています。いずれバナーをやめてスペースを食わない広告も勉強する必要があります。 ご紹介頂いた「リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック 」はAmazonの到着待ちです。今後ともご指導ください。 今日はこれからSwift in Yokohamaのもくもく会に行き最後の仕上げをしたいと思っています。全部完成したら質問の編集機能で報告をしたいと思います。
Tomzy

2017/11/26 01:00

xAxisさんへ 体調をくずされたとのことですが、大事になさってください、 そういう時にまでコメント本当にありがとうございます。 広島でご披露させて頂きます。
xAxis

2017/11/29 10:40

遅くなってしまったこと、そして質問依頼を頂いていたのに反応できずにすいませんでした。 > Undo is astonishingly hard. astonishinglyという表現は知らなかったので調べてみたら強めの表現なのですね。 確かにUndoやRedoの実装はかなり大変そうに思います。。。 こちらでも調べてみたのですが関数型プログラミングで行う方法、デザインパターンを利用して行う方法、気合いで実装する方法等あるようですが、 前二つは学習コストがかかること、気合いで実装するにはロジックを考えるのがかなり大変だと思います。 多機能だとユーザ側としても嬉しい反面、1画面が持つ情報量が多すぎるとユーザが情報を処理出来なくなるということもありますし次回のバージョンアップへ持ち越しもありだと思います。 > 今後ともご指導ください。 僕の方こそ新たな知見を得られることはもちろん、Tomzyさんがお持ちの大きな意欲に大変刺激を受けていますから指導なんて恐れ多いですwですがもし私が答えられる範囲でしたらご遠慮なくどうぞ。微力ながら尽力致します。 お気遣いの言葉ありがとうございます。嬉しく思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問