teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

2

加筆・修正

2017/11/15 08:43

投稿

xAxis
xAxis

スコア1349

answer CHANGED
@@ -1,3 +1,7 @@
1
+ ###前書き
2
+
3
+ これは絵文字のことを**考慮していない**アプローチです。絵文字を考慮したものは回答の最下部に書いてみました。
4
+
1
5
  ###実装の流れ
2
6
 
3
7
  今回実装したい機能の流れとしては
@@ -80,4 +84,77 @@
80
84
  //(5)
81
85
  let modifiedText = leftText + rightText
82
86
  textView.text = String(modifiedText)
87
+ ```
88
+
89
+ ###絵文字が混ざった場合
90
+
91
+ ```swift
92
+
93
+ /*
94
+ 事前情報
95
+
96
+ 今回絵文字が含まれているのでUTF16に関する知識が多少必要になり、さらにアプローチが上記のものとは部分的に変わっています。
97
+ */
98
+
99
+ /*
100
+ アプローチについて
101
+
102
+ 絵文字を考慮しつつカーソルの右側を削除する場合、考えることは
103
+ 1.入力された全ての文字列
104
+ 2.startIndexからカーソルまでの文字列
105
+ 3.削除する文字数
106
+ 4.カーソルの右側に残った文字列の数
107
+ 5.UTF16の文字列は「あ」という文字は1文字として認識されるが「????」という文字は2文字として認識される。さらに色付きの文字だったりすると倍になったりする(詳細はUTF16, unicode等検索して下さい)
108
+ 6.5番を踏まえるとカーソルの右が普通の文字であろうと絵文字であろうと対応できるコードを書かなければならない
109
+
110
+ 1.はfullText
111
+ 2.はfullText[startIndex..<cursorIndex]
112
+ 3.はカーソルの右側1文字だけなのでamountOfCharacter = 1
113
+ 4.は実機等画面に表示されている全ての文字列の数 - leftTextの数 - ammountOfCharacter
114
+ で表します。
115
+ 5.これのおかげでUITextView.index(_,offsetBy:)で隣の文字を取得するという方法が使えません。
116
+ 6.なので4番で取得した文字列分fullTextから切り取る、という方法を取ります。
117
+ */
118
+
119
+ //(1)
120
+ guard let fullText = textView.text else {
121
+ return
122
+ }
123
+
124
+ //UITextView.selectedRange.locationで取れる位置はUTF16で換算されているようです(要検証)
125
+ let offset = textView.selectedRange.location
126
+ let count = fullText.utf16.count
127
+
128
+ if offset >= count {
129
+ return
130
+ }
131
+
132
+ //(2)
133
+ //文字列の最初のインデックス情報
134
+ let startIndex = fullText.utf16.startIndex
135
+ //カーソルのインデックス情報
136
+ let cursorIndex = fullText.utf16.index(startIndex, offsetBy: offset)
137
+ //文字列の最初からカーソルまでの文字列
138
+ let leftText = fullText[startIndex..<cursorIndex]
139
+
140
+ //(3),(4)
141
+ //文字数の把握及び計算
142
+ let amountOfFullTextCharacter = fullText.underestimatedCount
143
+ let amountOfLeftTextCharacter = String(leftText).underestimatedCount
144
+ let amountOfDeleteCharacter = 1
145
+ let remain = amountOfFullTextCharacter - amountOfLeftTextCharacter - amountOfDeleteCharacter
146
+
147
+ //(5),(6)
148
+ //文字列の末尾からremain分切り取り
149
+ let rightText = fullText.suffix(remain)
150
+
151
+ //文字列の結合。カーソルの左側を削除した文字列になる
152
+ let modifiedText = leftText + rightText
153
+
154
+ //modifiedTextはsubstringでStringではないのでStringにしてUITextView.textに代入
155
+ textView.text = String(modifiedText)
156
+
157
+ //カーソルの位置を元に戻す
158
+ textView.selectedRange.location = offset
159
+
83
160
  ```

1

修正

2017/11/15 08:43

投稿

xAxis
xAxis

スコア1349

answer CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  今回実装したい機能の流れとしては
4
4
 
5
+ 0. テキストがない場合抜ける
5
- 0. カーソルの位置が一番右にある場合抜ける
6
+ 0. カーソルの位置が一番右にある場合抜ける
6
- 0. テキストがない場合も抜ける
7
7
  0. ターゲットとする文字の左側のインデックスおよび右側のインデックスの取得を行う。
8
8
  0. 文字列の頭からターゲットの左側までの文字列およびターゲットの右側から文字列の最後尾の二つを取得する
9
9
  0. 4で取得した文字列の結合
10
10
 
11
11
  となります。
12
- やることは前回した回答と同じような流れではあるのですが1の場合の抜け方とtargetOfLeftIndexおよびtargetOfRightIndexの取得方法が若干異なります。
12
+ やることは前回した回答と同じような流れではあるのですが2の場合の抜け方とtargetOfLeftIndexおよびtargetOfRightIndexの取得方法が若干異なります。
13
13
 
14
14
  ###型の違い
15
15
  前回の質問の段階でもっと丁寧にしておけばと思っていたのですが今回その機会がやってきたので少し説明をしておきます。今回出てくる定数のうちstartIndex, endIndex, targetOfLefindex, targetOfRightIndexはString.indexという少々特殊な型です。が一方でoffsetは基本的なIntになります。後述してあるfullText.countも同様にIntです。型が違うのでそのままたし算、ひき算は出来ません。
@@ -39,8 +39,8 @@
39
39
 
40
40
  ###コーディングの前準備
41
41
 
42
- 前回した回答との違いは実のところ1と3だけになります。
42
+ 前回した回答との違いは実のところ2と3だけになります。
43
- 前回の質問では1の部分は「カーソルの位置が一番左にある場合抜ける」でした。
43
+ 前回の質問では2の部分は「カーソルの位置が一番左にある場合抜ける」でした。
44
44
  そしてカーソルの位置が一番左の時を表すコードは
45
45
  ```
46
46
  offset == 0
@@ -55,10 +55,12 @@
55
55
  ```swift
56
56
 
57
57
 
58
- //(2)
58
+ //(1)
59
59
  guard let fullText = textView.text else {
60
60
  return
61
+ }
62
+
61
- }//(1)
63
+ //(2)
62
64
  let count = fullText.count
63
65
  let offset = textView.selectedRange.location
64
66
  if offset >= count {