무한정 길이가 늘어나는 textEditor일때 커서가 내려가면 scrollView의 스크롤 위치도 그에 따라 변하게 하고 싶었다
그래서 열심히 찾아봤지만 순수 SwiftUI를 통해서는 구현이 힘들었고 Introspect라이브러리를 이용해서 구현했다!

uiKit에서 textView와 비슷한 역할을 하는 애라고 생각하면 된다!(물론 uikit의 textView에 비해서 없는 기능이 많아서 완전 순정으로 모든 uikit의 textView구현하는 거는 힘들 수도,,,)
애플 문서에서는 'A view that can display and edit long-form text.'라고 나와있다. 즉, 긴문자를 편집하거나 보여주는데 사용하는 뷰이다.
더 자세한 내용은 애플 공식문서를 참고하자!
SwiftUI만 이용해서 해당 기능을 구현하는 것은 아직 없는 것는 것 같아서 해당 라이브러리를 이용했는데 IntroSpect는 SwiftUI에서 쉽게 UIKit의 기능을 사용할 수 있도록 해주는 라이브러리다.
원래 SwiftUI에서 UIKit을 사용하려면 UIRepresentable이나 별도 커스텀뷰를 이용했어야 했는데 그런 번거로운 작업을 하지 않아도 쉽게 사용할 수 있도록 도와주는 라이브러리다.
but,,, 약간 불편할 수 있고 취향을 타는 라이브러리라고 한다. 그리고 후에 지원이 중단될 수 있다는 단점이 있다!
일단 찾아본 결과 순수하게 SwiftUI만 이용해서 구현할 수 있는 방법은 없는 것 같았다(애플 빨리 추가해조,,)
그래서 SwiftUI + introspect라이브러리를 조합해서 구현했다
두가지 방법이 있다. 첫번째는 spm 두번째는 cocoapods으로 설치하기
나 같은 경우에는 cocoapods으로 설치했다.
pod 'SwiftUIIntrospect', '~> 1.0'
혹은
let package = Package(
dependencies: [
.package(url: "https://github.com/siteline/swiftui-introspect", from: "1.0.0"),
],
targets: [
.target(name: <#Target Name#>, dependencies: [
.product(name: "SwiftUIIntrospect", package: "swiftui-introspect"),
]),
]
)
설치하고 사용하려고 하는 view에 import를 해주면 된다
import SwiftUI
import SwiftUIIntrospect
@State 변수로 UITextView를 만들어서 intropect가 가져올 uiTextView를 받아준다TextEditor(text: $reviewText)
.introspect(.textEditor, on: .iOS(.v15, .v16, .v17), customize: { uiTextView in
DispatchQueue.main.async {
self.uiTextView = uiTextView
}
})
@State로 cursorPosition 변수를 만들어준다(textView의 텍스트들이 변경될때마다 해당 변경을 감지해야 하므로 onChange를 이용해야 한다).onChange(of: reviewText) { newValue in
if let textView = uiTextView {
if let range = textView.selectedTextRange {
let cursorPosition = textView.offset(from: textView.beginningOfDocument, to: range.start)
self.cursorPosition = cursorPosition
}
}
}
cf) selectedTextRange란
문서 내에서 선택한 텍스트의 범위이다.
문서가 길이가 있으면 그게 현재 선택된 텍스트이고 텍스트가 nil이면 선택된 문서가 없는 것을 뜻한다
-> 즉, 커서의 위치를 가지고 오는 코드이다
cf) offset(from:to:)란
한 텍스트 위치와 다른 텍스트 위치 사이의 UTF-16 문자 수를 반환한다
cf) textView.beginningOfDocument란?
텍스트의 시작 위치를 가져온다
cf) range.start
텍스트 특정 범위의 시작위치를 의미한다(여기서는 특정 범위가 선택되지 않았으니까 커서의 위치라고 할 수 있다)
.onChange(of: cursorPosition) { pos in
proxy.scrollTo(cursorPositionId)
}
minHeight이용) text높이에 따라 textEditor가 늘어날 수 있도록 fixedSize를 적용해준다@Namespace var cursorPositionId
@State private var reviewText: String = ""
@State private var uiTextView: UITextView?
@State private var cursorPosition: Int = 0
var body: some View {
ScrollViewReader { proxy in
ScrollView {
TextEditor(text: $reviewText)
.introspect(.textEditor, on: .iOS(.v15, .v16, .v17), customize: { uiTextView in
DispatchQueue.main.async {
self.uiTextView = uiTextView
}
})
.onChange(of: reviewText) { newValue in
if let textView = uiTextView {
if let range = textView.selectedTextRange {
let cursorPosition = textView.offset(from: textView.beginningOfDocument, to: range.start)
self.cursorPosition = cursorPosition
}
}
}
.id(cursorPositionId)
.font(.callout)
.frame(minHeight: 246, alignment: .topLeading)
.background(Color.gray)
.fixedSize(horizontal: false, vertical: true)
}
.onChange(of: cursorPosition) { pos in
proxy.scrollTo(cursorPositionId)
}
}
.padding()
}
해당 코드랑 비슷하게 해서 프로젝트를 출시했는데 아이폰 15프로와 아이폰 14프로의 경우 특정 핸드폰에서 텍스트를 엄청나게 길게 쓰면 에러가 있는 것 같았다ㅠㅠ
그냥 uiKit의 텍스트를 가지고 와서 쓰는게 좋은 방법일 수도 있다ㅠ
ㅠㅠㅠ 아니면 다른 방법이 있는지 찾아봐야 할 것 같다,,,,
해당 코드 깃허브
https://stackoverflow.com/questions/70211903/scrolling-to-texteditor-cursor-position-in-swiftui