Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- combine
- map
- swiftUI
- UITextView
- HIG
- Protocol
- Xcode
- rxswift
- tableView
- MVVM
- uiscrollview
- collectionview
- Observable
- 리펙터링
- RxCocoa
- 리펙토링
- SWIFT
- uitableview
- clean architecture
- ios
- 스위프트
- 클린 코드
- 애니메이션
- UICollectionView
- ribs
- Human interface guide
- Refactoring
- 리팩토링
- swift documentation
- Clean Code
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 1. 붙여넣기 글자 제한 UITextView 처리, 커서 이동 처리 방법 - 단순 텍스트 본문
iOS 응용 (swift)
[iOS - swift] 1. 붙여넣기 글자 제한 UITextView 처리, 커서 이동 처리 방법 - 단순 텍스트
jake-kim 2023. 9. 19. 00:091. 붙여넣기 글자 제한 UITextView 처리, 커서 이동 처리 방법 - 단순 텍스트
2. 붙여넣기 글자 제한 UITextView 처리, 커서 이동 처리 방법 - UTF16 (이모지를 고려한 처리)
붙여넣기 글자 제한 로직
- 123과 456 사이에 최대 글자 300을 넘는 문자열을 붙여넣는 경우
- 요구사항1) 123과 456사이에 붙여넣기 적용, 300자가 넘는 문자열은 뒤에가 잘리도록 처리
- 요구사항2) 붙여넣기 이후 커서 위치는 붙여넣은 문자열 바로 뒤에 위치
- 요구사항3) 이미 300자가 초과하는 부분 중간에 붙여넣기 > 커서 위치가 제자리
- 커스텀 처리를 안해주면, 디폴트는 붙여넣은 만큼 이동되므로 어색한 현상 발생
글자 제한 로직 구현에 앞서, 예제 UI 준비
- 필요한 예제 UI 코드 준비
import UIKit
class ViewController: UIViewController {
private let textView = {
let view = UITextView()
view.textColor = .black
view.backgroundColor = .lightGray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let label: UILabel = {
let label = UILabel()
label.text = "0/0"
label.textColor = .blue
label.font = .systemFont(ofSize: 24, weight: .regular)
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let maxCount = 300
private var textCount = 0 {
didSet { label.text = "\(textCount)/\(maxCount)" }
}
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
view.addSubview(textView)
view.addSubview(label)
NSLayoutConstraint.activate([
textView.heightAnchor.constraint(equalToConstant: 300),
textView.widthAnchor.constraint(equalToConstant: 300),
textView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
textView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}
}
- 문자열 처리에 사용될 substring, inserting 메소드 정의
extension String {
func substring(from: Int, to: Int) -> String {
guard from < count, to >= 0, to - from >= 0 else { return "" }
let startIndex = index(startIndex, offsetBy: from)
let endIndex = index(startIndex, offsetBy: to + 1)
return String(self[startIndex ..< endIndex])
}
func inserting(_ string: String, at index: Int) -> String {
var originalString = self
originalString.insert(contentsOf: string, at: self.index(self.startIndex, offsetBy: index))
return originalString
}
}
글자 제한 로직 구현
- 문자열 처리 - shouldChangeTextIn 델리게이트 메소드에서 구현
extension ViewController: UITextViewDelegate {
func textView(
_ textView: UITextView,
shouldChangeTextIn range: NSRange,
replacementText text: String
) -> Bool {
// TODO ...
}
}
- 글자 제한 로직 아이디어
- 제한 글자수를 넘게 입력된 경우 (붙여넣기로 1000자 입력한 경우): textView.text에 직접 값 입력해주고 false를 리턴
- 제한 글자수를 넘지 않게 입력된 경우: 단순히 true 리턴
- 현재 입력한 텍스트(lastText), 현재 입력한 텍스트를 포함한 전체 텍스트(allText) 준비
- defer 에서 textCount에 업데이트하여 UILabel에 현재까지 입력된 정보 표출
let lastText = textView.text as NSString
let allText = lastText.replacingCharacters(in: range, with: text)
let canUseInput = allText.count <= maxCount
defer {
if canUseInput {
textCount = allText.count
} else {
textCount = textView.text.count
}
}
// TODO...
- canUseInput이 true이면 바로 입력되게하면 되고, false이면 textView.text에 직접 입력해줘야 하므로 guard문으로 처리
guard !canUseInput else { return canUseInput }
// TODO...
- 분기문 추가
- 현재까지 입력된 문자열이 maxCount를 넘지 않은 경우에는 문자열 붙여넣기 처리를 따로 해주는 부분
- 현재까지 입력된 문자열이 maxCount를 이미 넘는 경우, 붙여넣기해도 커서가 제자리로 이동시키는 부분
if textView.text.count < maxCount {
/// "abc{최대글자가 넘는 문자열 붙여넣기}def"
/// 기대결과: "abc{문자열}def"
} else {
/// 카운트 값을 넘었을때 중간 커서에서 붙여넣기 > 커서가 문자열만큼 뒤로 가는 버그 > 다시 커서 제자리로 위치시키는 코드
}
return canUseInput
- 첫번째 if문을 처리
- 1) 새로 추가할 문자열들을 최대 개수만큼 자름
- 2) 현재 커서로부터 문자열을 추가
- 3) 커서 이동: 현재 커서로부터 추가된 문자열의 길이만큼 더해줌
if textView.text.count < maxCount {
/// "abc{최대글자가 넘는 문자열 붙여넣기}def"
/// 기대결과: "abc{문자열}def"
// 1) 새로 추가할 문자열들을 최대 개수만큼 자름
let appendingText = text.substring(from: 0, to: maxCount - textView.text.count - 1)
// 2) 현재 커서로부터 문자열을 추가
textView.text = textView.text.inserting(appendingText, at: range.lowerBound)
// 3) 커서 이동: 현재 커서로부터 추가된 문자열의 길이만큼 더해줌
let isLastCursor = range.lowerBound >= textView.text.count
let movingCursorPosition = isLastCursor ? maxCount : (range.lowerBound + appendingText.count)
DispatchQueue.main.async {
textView.selectedRange = NSMakeRange(movingCursorPosition, 0)
}
} else {
// TODO...
}
- else부분 구현
- 붙여넣기해도 커서가 제자리로 이동되어야하므로, 단순히 range.lowerBound로 위치
DispatchQueue.main.async {
textView.selectedRange = NSMakeRange(range.lowerBound, 0)
}
(완료)
cf) UTF16를 사용하고 있으므로 🇰🇷 이모지의 크기는 4이며, 1이 아닌 것에 주의
- "some string".count의 값은 단순히 글자 수이며, shouldChangeTextIn에서 현재 포커스를 구할 때 NSRange를 사용하는데 이 값 기준은 UTF16으로 글자의 크기 기준이므로 처리할 때 .count로 처리하면 안되므로 주의
- UTF16를 고려한 처리는 다음 포스팅 글에서 계속
* 전체 코드: https://github.com/JK0369/ExLimitedString
* 참고
'iOS 응용 (swift)' 카테고리의 다른 글
Comments