@@ -135,7 +135,7 @@ private struct UIKitTextEditorRepresentable: UIViewRepresentable {
135135 if let focusBinding {
136136 if focusBinding. wrappedValue {
137137 if !uiView. isFirstResponder {
138- context. coordinator. preserveAncestorScrollOffset ( for: uiView)
138+ context. coordinator. startTrackingOffset ( for: uiView)
139139 uiView. becomeFirstResponder ( )
140140 }
141141 } else if uiView. isFirstResponder {
@@ -148,15 +148,17 @@ private struct UIKitTextEditorRepresentable: UIViewRepresentable {
148148
149149 final class Coordinator : NSObject , UITextViewDelegate {
150150 var parent : UIKitTextEditorRepresentable
151- private weak var ancestorScrollView : UIScrollView ?
152- private var preservedContentOffset : CGPoint ?
151+ private weak var scrollView : UIScrollView ?
152+ private var offsetObservation : NSKeyValueObservation ?
153+ private var trackedOffset : CGPoint ?
154+ private var isRestoringOffset = false
153155
154156 init ( _ parent: UIKitTextEditorRepresentable ) {
155157 self . parent = parent
156158 }
157159
158160 func textViewShouldBeginEditing( _ textView: UITextView ) -> Bool {
159- preserveAncestorScrollOffset ( for: textView)
161+ startTrackingOffset ( for: textView)
160162 return true
161163 }
162164
@@ -170,20 +172,16 @@ private struct UIKitTextEditorRepresentable: UIViewRepresentable {
170172 focusBinding. wrappedValue = true
171173 }
172174
173- restoreAncestorScrollOffsetIfNeeded ( )
175+ restoreOffsetIfNeeded ( )
174176
175177 DispatchQueue . main. async { [ weak self] in
176- self ? . restoreAncestorScrollOffsetIfNeeded ( )
178+ self ? . restoreOffsetIfNeeded ( )
177179 self ? . updateHeight ( for: textView)
178180 }
179-
180- DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) { [ weak self] in
181- self ? . restoreAncestorScrollOffsetIfNeeded ( )
182- self ? . preservedContentOffset = nil
183- }
184181 }
185182
186183 func textViewDidChange( _ textView: UITextView ) {
184+ stopTrackingOffset ( )
187185 parent. text = textView. text
188186 updateHeight ( for: textView)
189187 }
@@ -193,6 +191,7 @@ private struct UIKitTextEditorRepresentable: UIViewRepresentable {
193191 focusBinding. wrappedValue = false
194192 }
195193
194+ stopTrackingOffset ( )
196195 applyPlaceholderIfNeeded ( to: textView)
197196 }
198197
@@ -210,19 +209,65 @@ private struct UIKitTextEditorRepresentable: UIViewRepresentable {
210209 textView. textColor == . placeholderText
211210 }
212211
213- func preserveAncestorScrollOffset( for textView: UITextView ) {
214- ancestorScrollView = textView. enclosingScrollView
215- preservedContentOffset = ancestorScrollView? . contentOffset
212+ func startTrackingOffset( for textView: UITextView ) {
213+ stopObservingOffset ( )
214+ scrollView = textView. enclosingScrollView
215+ trackedOffset = scrollView? . contentOffset
216+ observeOffsetIfNeeded ( )
217+ }
218+
219+ func restoreOffsetIfNeeded( ) {
220+ guard let scrollView, let trackedOffset else { return }
221+
222+ if scrollView. contentOffset != trackedOffset {
223+ isRestoringOffset = true
224+ scrollView. setContentOffset ( trackedOffset, animated: false )
225+ isRestoringOffset = false
226+ }
216227 }
217228
218- func restoreAncestorScrollOffsetIfNeeded ( ) {
219- guard let ancestorScrollView , let preservedContentOffset else { return }
229+ func observeOffsetIfNeeded ( ) {
230+ guard let scrollView else { return }
220231
221- if ancestorScrollView. contentOffset != preservedContentOffset {
222- ancestorScrollView. setContentOffset ( preservedContentOffset, animated: false )
232+ offsetObservation = scrollView. observe (
233+ \. contentOffset,
234+ options: [ . new]
235+ ) { [ weak self] scrollView, _ in
236+ self ? . handleOffsetChange ( in: scrollView)
223237 }
224238 }
225239
240+ func handleOffsetChange( in scrollView: UIScrollView ) {
241+ guard let trackedOffset else {
242+ stopObservingOffset ( )
243+ return
244+ }
245+
246+ if scrollView. isTracking || scrollView. isDragging || scrollView. isDecelerating {
247+ stopTrackingOffset ( )
248+ return
249+ }
250+
251+ if isRestoringOffset {
252+ return
253+ }
254+
255+ if scrollView. contentOffset != trackedOffset {
256+ restoreOffsetIfNeeded ( )
257+ }
258+ }
259+
260+ func stopObservingOffset( ) {
261+ offsetObservation? . invalidate ( )
262+ offsetObservation = nil
263+ }
264+
265+ func stopTrackingOffset( ) {
266+ stopObservingOffset ( )
267+ scrollView = nil
268+ trackedOffset = nil
269+ }
270+
226271 func updateHeight( for textView: UITextView ) {
227272 textView. layoutIfNeeded ( )
228273
0 commit comments