【Swift】TextViewで選択した部分の文字列を取得
実現したいこと
- 先頭~途中/末尾までの文字を取得
- 途中~末尾までの文字を取得
- 途中~途中までの文字を取得
仕組み
- textViewの文字列を選択
- 選択した文字列の開始位置、長さを取得
- カーソルが1文字以上を選択してるかを条件分岐
- 選択した文字列の開始位置、終了位置を取得
- 部分文字列を取得
textViewの文字列を選択
選択した文字列の開始位置、長さを取得
// 開始位置 let location = textView.selectedRange.location // 長さ let length = textView.selectedRange.length
文字列の取得に必要。
カーソルが1文字以上を選択してるかを条件分岐
if length <= 0 { return } else { // 文字列を取得する処理 }
文字選択の際に表示されるカーソルには、何も選択していない下記の状態が存在する。
↓
もし条件分岐をせずに文字列を取得しようとしたら、下記のエラーが発生。
↓
Thread 1: Fatal error: String index is out of bounds
エラー箇所は文字列を取得する下記のコード。
(原因:文字列が選択されていない → locationとlengthが0 → offsetByが-1)
↓
let endIndex = textView.text.index(strIndex, offsetBy: location+length-1, limitedBy: textView.text.endIndex)
上記のエラーを回避するため、
- カーソルが文字列を選択
- カーソルが文字列を選択してない
場合で処理を分岐させた。
選択した文字列の開始位置、終了位置を取得
// strIndex:0番目(Index型の0) let strIndex = textView.text.startIndex // Index型の開始位置、終了位置 guard let startIndex = textView.text.index(strIndex, offsetBy: location, limitedBy: textView.text.endIndex), let endIndex = textView.text.index(strIndex, offsetBy: location+length-1, limitedBy: textView.text.endIndex) else { return }
仕組みを例えると、
- String型“123456”がある
- “45”を選択
- location = 3, length = 2
- startIndex = 3, endIndex = 4
startIndex, endIndexはIndex型。
(Appleのドキュメントより)
Index型とは、文字の場所を示す値。
printで出力すると、不規則な数字が並ぶので、中身は気にしなくていい。
print(startIndex) // Index(_rawBits: 131329)
部分文字列を取得
textView.textの要素(文字)を、Index型の変数(startIndex, endIndex)を使い、範囲で指定。
↓
print(String(textView.text[startIndex...endIndex])) // 選択した文字列 print(type(of: textView.text[startIndex...endIndex])) // SubString
仕組みを例えると、
- 変数text = “123456”がある
- “45”を選択
- location = 3, length = 2
- startIndex = 3, endIndex = 4
- text[startIndex...endIndex] = “45”
startIndexとendIndexでどの範囲の文字列を取得するかを指定している。
Appleのドキュメント、SubStringはStringを分割したもの。
そのままでもprint()で出力はできても、Labelのテキストなどに代入するなら、String型にキャストする必要がある。
(でないと、下記のエラーが出る) ↓
Subscript 'subscript(_:)' requires the types 'String.Index' and 'Int' be equivalent
ストーリーボード
ソースコード
import UIKit class ViewController: UIViewController { @IBOutlet weak var selectedString: UILabel! @IBOutlet weak var textView: UITextView! override func viewDidLoad() { super.viewDidLoad() textView.delegate = self } } extension ViewController: UITextViewDelegate { // textViewDidChangeSelection:textViewの文字が選択されたら処理 func textViewDidChangeSelection(_ textView: UITextView) { let location = textView.selectedRange.location let length = textView.selectedRange.length if length <= 0 { selectedString.text?.removeAll() return } else { let strIndex = textView.text.startIndex guard let startIndex = textView.text.index(strIndex, offsetBy: location, limitedBy: textView.text.endIndex), let endIndex = textView.text.index(strIndex, offsetBy: location+length-1, limitedBy: textView.text.endIndex) else { return } selectedString.text = String(textView.text[startIndex...endIndex]) } } }