Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Textfield should scroll to follow the cursor when moving it with the …

…keyboard
  • Loading branch information...
commit ca3845cf229117e3e48cd314a6135136729f287d 1 parent 2ca1fdd
Michael Villar authored
7 lib/UIKit/TUITextEditor.m
@@ -17,6 +17,10 @@
17 17 #import "TUIKit.h"
18 18 #import "TUITextEditor.h"
19 19
  20 +@interface TUITextRenderer ()
  21 +- (void)_scrollToIndex:(long)index;
  22 +@end
  23 +
20 24 @implementation TUITextEditor
21 25
22 26 @synthesize defaultAttributes;
@@ -100,6 +104,7 @@ - (void)cut:(id)sender
100 104 - (void)paste:(id)sender
101 105 {
102 106 [self insertText:[[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString]];
  107 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
103 108 }
104 109
105 110 - (void)keyDown:(NSEvent *)event
@@ -132,6 +137,7 @@ - (void)deleteCharactersInRange:(NSRange)range // designated delete
132 137 selectedRange.length = 0;
133 138 self.selectedRange = selectedRange;
134 139 [self _textDidChange];
  140 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
135 141 }
136 142
137 143
@@ -183,6 +189,7 @@ - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange // des
183 189 [self unmarkText];
184 190 self.selectedRange = selectedRange;
185 191 [self _textDidChange];
  192 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
186 193 }
187 194
188 195 /* The receiver inserts aString replacing the content specified by replacementRange.
38 lib/UIKit/TUITextRenderer+KeyBindings.m
@@ -17,11 +17,13 @@
17 17 #import "TUITextRenderer.h"
18 18 #import "TUITextEditor.h"
19 19 #import "TUIView.h"
  20 +#import "TUIScrollView.h"
20 21 #import "CoreText+Additions.h"
21 22
22 23 @interface TUITextRenderer ()
23 24
24 25 - (CTFrameRef)ctFrame;
  26 +- (void)_scrollToIndex:(long)index;
25 27
26 28 @end
27 29
@@ -80,6 +82,20 @@ - (TUITextEditor *)_textEditor
80 82 return nil;
81 83 }
82 84
  85 +- (void)_scrollToIndex:(long)index
  86 +{
  87 + if(self.view && [self.view.superview isKindOfClass:[TUIScrollView class]])
  88 + {
  89 + TUIScrollView *scrollView = (TUIScrollView*)self.view.superview;
  90 + CFRange r = { index, 0 };
  91 + CFIndex nRects = 1;
  92 + CGRect rects[nRects];
  93 + AB_CTFrameGetRectsForRange(TEXT, [self ctFrame], r, rects, &nRects);
  94 + if(nRects == 1)
  95 + [scrollView scrollRectToVisible:rects[0] animated:YES];
  96 + }
  97 +}
  98 +
83 99 - (int)_indexByMovingIndex:(int)index
84 100 by:(int)incr
85 101 {
@@ -116,6 +132,7 @@ - (void)moveUp:(id)sender
116 132 _selectionEnd = _selectionStart = [self _indexByMovingIndex:(int)MIN(_selectionStart,_selectionEnd)
117 133 by:-1];
118 134 [self.view setNeedsDisplay];
  135 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
119 136 }
120 137
121 138 - (void)moveUpAndModifySelection:(id)sender
@@ -123,6 +140,7 @@ - (void)moveUpAndModifySelection:(id)sender
123 140 _selectionEnd = [self _indexByMovingIndex:(int)MIN(_selectionStart,_selectionEnd)
124 141 by:-1];
125 142 [self.view setNeedsDisplay];
  143 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
126 144 }
127 145
128 146 - (void)moveDown:(id)sender
@@ -134,6 +152,7 @@ - (void)moveDown:(id)sender
134 152 _selectionEnd = _selectionStart = [self _indexByMovingIndex:(int)MAX(_selectionStart,_selectionEnd)
135 153 by:1];
136 154 [self.view setNeedsDisplay];
  155 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
137 156 }
138 157
139 158 - (void)moveDownAndModifySelection:(id)sender
@@ -141,6 +160,7 @@ - (void)moveDownAndModifySelection:(id)sender
141 160 _selectionEnd = [self _indexByMovingIndex:(int)MAX(_selectionStart,_selectionEnd)
142 161 by:1];
143 162 [self.view setNeedsDisplay];
  163 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
144 164 }
145 165
146 166 - (void)moveRight:(id)sender
@@ -149,6 +169,7 @@ - (void)moveRight:(id)sender
149 169 NSInteger max = [TEXT length];
150 170 _selectionStart = _selectionEnd = MIN(MAX(_selectionStart, _selectionEnd) + (selectionLength?0:1), max);
151 171 [self.view setNeedsDisplay];
  172 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
152 173 }
153 174
154 175 - (void)moveLeft:(id)sender
@@ -157,6 +178,7 @@ - (void)moveLeft:(id)sender
157 178 NSInteger min = 0;
158 179 _selectionStart = _selectionEnd = MAX(MIN(_selectionStart, _selectionEnd) - (selectionLength?0:1), min);
159 180 [self.view setNeedsDisplay];
  181 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
160 182 }
161 183
162 184 - (void)moveRightAndModifySelection:(id)sender
@@ -164,6 +186,7 @@ - (void)moveRightAndModifySelection:(id)sender
164 186 NSInteger max = [TEXT length];
165 187 _selectionEnd = MIN(_selectionEnd + 1, max);
166 188 [self.view setNeedsDisplay];
  189 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
167 190 }
168 191
169 192 - (void)moveLeftAndModifySelection:(id)sender
@@ -171,64 +194,75 @@ - (void)moveLeftAndModifySelection:(id)sender
171 194 NSInteger min = 0;
172 195 _selectionEnd = MAX(_selectionEnd - 1, min);
173 196 [self.view setNeedsDisplay];
  197 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
174 198 }
175 199
176 200 - (void)moveWordRight:(id)sender
177 201 {
178 202 _selectionStart = _selectionEnd = [TEXT ab_endOfWordGivenCursor:MAX(_selectionStart, _selectionEnd)];
179 203 [self.view setNeedsDisplay];
  204 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
180 205 }
181 206
182 207 - (void)moveWordLeft:(id)sender
183 208 {
184 209 _selectionStart = _selectionEnd = [TEXT ab_beginningOfWordGivenCursor:MIN(_selectionStart, _selectionEnd)];
185 210 [self.view setNeedsDisplay];
  211 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
186 212 }
187 213
188 214 - (void)moveWordRightAndModifySelection:(id)sender
189 215 {
190 216 _selectionEnd = [TEXT ab_endOfWordGivenCursor:_selectionEnd];
191 217 [self.view setNeedsDisplay];
  218 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
192 219 }
193 220
194 221 - (void)moveWordLeftAndModifySelection:(id)sender
195 222 {
196 223 _selectionEnd = [TEXT ab_beginningOfWordGivenCursor:_selectionEnd];
197 224 [self.view setNeedsDisplay];
  225 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
198 226 }
199 227
200 228 - (void)moveToBeginningOfLineAndModifySelection:(id)sender
201 229 {
202 230 _selectionEnd = 0; // fixme for multiline
203 231 [self.view setNeedsDisplay];
  232 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
204 233 }
205 234
206 235 - (void)moveToEndOfLineAndModifySelection:(id)sender
207 236 {
208 237 _selectionEnd = [TEXT length]; // fixme for multiline
209 238 [self.view setNeedsDisplay];
  239 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
210 240 }
211 241
212 242 - (void)moveToBeginningOfLine:(id)sender
213 243 {
214 244 _selectionStart = _selectionEnd = 0;
215 245 [self.view setNeedsDisplay];
  246 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
216 247 }
217 248
218 249 - (void)moveToEndOfLine:(id)sender
219 250 {
220 251 _selectionStart = _selectionEnd = [TEXT length];
221 252 [self.view setNeedsDisplay];
  253 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
222 254 }
223 255
224 256 - (void)insertNewline:(id)sender
225 257 {
226 258 [[self _textEditor] insertText:@"\n"];
  259 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
227 260 }
228 261
229 262 - (void)insertNewlineIgnoringFieldEditor:(id)sender
230 263 {
231 264 [[self _textEditor] insertText:@"\n"];
  265 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
232 266 }
233 267
234 268 - (void)deleteBackward:(id)sender
@@ -245,6 +279,7 @@ - (void)deleteBackward:(id)sender
245 279 }
246 280
247 281 [[self _textEditor] deleteCharactersInRange:deleteRange];
  282 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
248 283 }
249 284
250 285 - (void)deleteForward:(id)sender
@@ -260,6 +295,7 @@ - (void)deleteForward:(id)sender
260 295 }
261 296
262 297 [[self _textEditor] deleteCharactersInRange:deleteRange];
  298 + [self _scrollToIndex:MAX(_selectionStart, _selectionEnd)];
263 299 }
264 300
265 301
@@ -271,6 +307,7 @@ - (void)deleteToBeginningOfLine:(id)sender
271 307 } else {
272 308 [self deleteBackward:nil];
273 309 }
  310 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
274 311 }
275 312
276 313 - (void)deleteWordBackward:(id)sender
@@ -282,6 +319,7 @@ - (void)deleteWordBackward:(id)sender
282 319 } else {
283 320 [self deleteBackward:nil];
284 321 }
  322 + [self _scrollToIndex:MIN(_selectionStart, _selectionEnd)];
285 323 }
286 324
287 325 @end

2 comments on commit ca3845c

How would it be possible to add this feature to the text view natively, without having its superview be a scroll view? I notice that most other APIs make the text view subclass the scroll view... I tried shifting the subclass (after sifting through the errors) and it just borked out on me.

Michael, this stuff is great. You're genius :]

Please sign in to comment.
Something went wrong with that request. Please try again.