@@ -30,6 +30,24 @@ open class FormattingTextField: UITextField {
30
30
}
31
31
}
32
32
33
+ open override var text : String ? {
34
+ get {
35
+ super. text
36
+ }
37
+
38
+ set {
39
+ let formatted = formattedText ( text: newValue)
40
+ let pos = currentPosition ( )
41
+ super. text = formatted
42
+ notifyDelegate ( text: text)
43
+ setCaretPositionAfterSettingText (
44
+ currentPosition: pos,
45
+ rawText: newValue,
46
+ formattedText: formatted
47
+ )
48
+ }
49
+ }
50
+
33
51
/// Formatter object in case you need your own formatting logic
34
52
open var formatter : AGFormatter ? { didSet { invalidateIntrinsicContentSize ( ) } }
35
53
@@ -57,25 +75,27 @@ open class FormattingTextField: UITextField {
57
75
58
76
//MARK: Overriden properties
59
77
open override var intrinsicContentSize : CGSize {
60
- guard let exampleMask = formattingMask ??
61
- formattingMask? . replacingOccurrences ( of: " # " , with: " 0 " ) ,
62
- !exampleMask. isEmpty // in case of monospaced digit fonts calculatiing width againts only digit text produces more accurate results
78
+ guard let exampleMask = formattingMask?
79
+ . replacingOccurrences ( of: " # " , with: " 0 " )
80
+ . replacingUnderscoresWithZeros ( ) ,
81
+ !exampleMask. isEmpty // in case of monospaced digit fonts calculatiing width againts only digit text produces more accurate results
63
82
else {
64
83
return super. intrinsicContentSize
65
84
}
66
85
67
- let font_ = font ?? UIFont . systemFont ( ofSize : 17 )
86
+ let font_ = font ?? UIFont . preferredFont ( forTextStyle : . body )
68
87
let height = font_. lineHeight
69
88
let width = sizeOfText ( exampleMask) . width
70
89
71
90
let caretWidth : CGFloat = caretRect ( for: endOfDocument) . width
91
+ let padding : CGFloat = 0
72
92
73
- return CGSize ( width: width + caretWidth, height: height)
93
+ return CGSize ( width: width + caretWidth + padding * 2 , height: height)
74
94
}
75
95
76
96
open override var font : UIFont ? {
77
97
didSet {
78
- minimumFontSize = font? . pointSize ?? 17
98
+ minimumFontSize = font? . pointSize ?? UIFont . systemFontSize
79
99
invalidateIntrinsicContentSize ( )
80
100
}
81
101
}
@@ -97,20 +117,7 @@ open class FormattingTextField: UITextField {
97
117
}
98
118
99
119
@objc internal func didChangeEditing( ) {
100
- var pos = currentPosition ( )
101
- let textCount = text? . count ?? 0
102
-
103
- let formatted = formattedText ( text: text)
104
- self . text = formatted
105
- notifyDelegate ( text: self . text)
106
- guard let last = text? . prefix ( pos) . last else { return }
107
-
108
- if !last. isNumber {
109
- pos = pos + 1 // не 1, а количество элементов до первой цифры с конца
110
- }
111
- if pos < textCount {
112
- setCursorPosition ( offset: pos)
113
- }
120
+ self . text = text
114
121
}
115
122
116
123
//MARK: UITextField methods overrides
@@ -160,7 +167,7 @@ open class FormattingTextField: UITextField {
160
167
if !range. isEmpty {
161
168
if mask. contains ( " * " ) || mask. contains ( " ? " ) {
162
169
let text = String ( txt. prefix ( currentPosition ( forStartOfRange: true ) ) )
163
- setFormattedText ( text)
170
+ self . text = text
164
171
return
165
172
}
166
173
} else if hasConstantPrefix && String ( txt. prefix ( cursorPosition - 1 ) ) == prefix {
@@ -170,7 +177,7 @@ open class FormattingTextField: UITextField {
170
177
if hasConstantPrefix && range. end == endOfDocument {
171
178
let stringByRemovingPrefix = String ( txt. prefix ( cursorPosition) . dropFirst ( prefix. count) )
172
179
if stringByRemovingPrefix. filter ( { $0. isLetter || $0. isNumber } ) . isEmpty {
173
- setFormattedText ( stringByRemovingPrefix)
180
+ self . text = stringByRemovingPrefix
174
181
return
175
182
}
176
183
}
@@ -184,16 +191,15 @@ open class FormattingTextField: UITextField {
184
191
185
192
charsToRemove += 1
186
193
txt. remove ( at: . init( utf16Offset: cursorPosition - charsToRemove, in: txt) )
187
-
188
- setFormattedText ( txt)
194
+ text = txt
189
195
setCursorPosition ( offset: cursorPosition - charsToRemove)
190
196
return
191
197
}
192
198
193
199
if !isNumberOrLetter( txt. dropLast ( ) . last) && range. end == endOfDocument {
194
200
let numberToDrop = min ( txt. count, 2 ) // what if last 2-3 symbols are invalid? is it possible?
195
201
txt. removeLast ( numberToDrop)
196
- setFormattedText ( txt)
202
+ text = txt
197
203
setCursorPosition ( offset: cursorPosition - numberToDrop)
198
204
return
199
205
}
@@ -206,7 +212,45 @@ open class FormattingTextField: UITextField {
206
212
}
207
213
}
208
214
215
+ open override func becomeFirstResponder( ) -> Bool {
216
+ if text. isEmptyOrTrue && !showsMaskIfEmpty && !_showsMask && formatter != nil {
217
+ _showsMask = true
218
+ setNeedsDisplay ( )
219
+ }
220
+ return super. becomeFirstResponder ( )
221
+ }
222
+
223
+ open override func resignFirstResponder( ) -> Bool {
224
+ if text. isEmptyOrTrue && !showsMaskIfEmpty && _showsMask && formatter != nil {
225
+ _showsMask = false
226
+ setNeedsDisplay ( )
227
+ }
228
+ return super. resignFirstResponder ( )
229
+ }
230
+
231
+ open override func textRect( forBounds bounds: CGRect ) -> CGRect {
232
+ guard let exampleMask = exampleMask? . replacingUnderscoresWithZeros ( ) else {
233
+ return super. editingRect ( forBounds: bounds)
234
+ }
235
+
236
+ let w = sizeOfText ( exampleMask) . width
237
+ let originX = ( bounds. width - w) / 2
238
+ return CGRect ( x: originX, y: 0 , width: w, height: bounds. height)
239
+ }
240
+
241
+ open override func editingRect( forBounds bounds: CGRect ) -> CGRect {
242
+ guard let exampleMask = exampleMask? . replacingUnderscoresWithZeros ( ) else {
243
+ return super. editingRect ( forBounds: bounds)
244
+ }
245
+
246
+ let caretWidth : CGFloat = caretRect ( for: endOfDocument) . width
247
+ let w = sizeOfText ( exampleMask) . width
248
+ let originX = ( bounds. width - w) / 2
249
+ return CGRect ( x: originX, y: 0 , width: w + caretWidth, height: bounds. height)
250
+ }
251
+
209
252
//MARK: Public methods
253
+ @available ( * , deprecated, renamed: " text " , message: " Use regular text setter to set formatted text programmatically " )
210
254
open func setFormattedText( _ text: String ? ) {
211
255
self . text = formattedText ( text: text)
212
256
notifyDelegate ( text: self . text)
@@ -218,12 +262,12 @@ open class FormattingTextField: UITextField {
218
262
219
263
open func drawExampleMask( rect: CGRect ) {
220
264
assertForExampleMasksAndPrefix ( )
221
- let text = self . text ?? " "
265
+ let text = text ?? " "
222
266
223
267
guard let mask = exampleMask,
224
268
!mask. isEmpty,
225
- let font = self . font ,
226
- let textColor = self . textColor ,
269
+ let font,
270
+ let textColor,
227
271
!text. isEmpty || _showsMask
228
272
else { return }
229
273
@@ -233,13 +277,22 @@ open class FormattingTextField: UITextField {
233
277
. foregroundColor : placeholderColor
234
278
] )
235
279
236
- if hasConstantPrefix {
280
+ if !text. isEmpty {
281
+ textToDraw. addAttributes (
282
+ [ . foregroundColor : UIColor . clear] ,
283
+ range: . init( location: 0 , length: text. count)
284
+ )
285
+ } else if hasConstantPrefix {
237
286
textToDraw. addAttributes (
238
287
[ . foregroundColor : textColor] ,
239
288
range: . init( location: 0 , length: prefix. count)
240
289
)
241
290
}
242
- textToDraw. draw ( at: CGPoint ( x: 0 , y: ( ( bounds. height - font. lineHeight) / 2 ) ) )
291
+
292
+ let w = sizeOfText ( mask. replacingUnderscoresWithZeros ( ) ) . width
293
+ let originX = ( bounds. width - w) / 2
294
+
295
+ textToDraw. draw ( at: CGPoint ( x: originX, y: ( ( bounds. height - font. lineHeight) / 2 ) ) )
243
296
}
244
297
245
298
open func formattedText( text: String ? ) -> String ? {
@@ -248,9 +301,7 @@ open class FormattingTextField: UITextField {
248
301
setNeedsDisplay ( )
249
302
}
250
303
}
251
- guard let formatter = formatter else {
252
- return text
253
- }
304
+ guard let formatter else { return text }
254
305
255
306
let result = formatter. formattedText ( text: text)
256
307
return result
@@ -271,19 +322,30 @@ open class FormattingTextField: UITextField {
271
322
}
272
323
}
273
324
274
- open override func becomeFirstResponder( ) -> Bool {
275
- if text. isEmptyOrTrue && !showsMaskIfEmpty && !_showsMask && formatter != nil {
276
- _showsMask = true
277
- setNeedsDisplay ( )
278
- }
279
- return super. becomeFirstResponder ( )
325
+ //MARK: Private & internal
326
+ internal func assertForExampleMasksAndPrefix( ) {
327
+ guard let mask = exampleMask, !mask. isEmpty, let formattingMask = formattingMask, formatter != nil else { return }
328
+ assert ( mask == formattedText ( text: mask) && mask. count == formattingMask. count, " Formatting mask and example mask should be in same format. This is your responsibility as a developer \n ExampleMask: \( mask) \n Formatting mask: \( formattingMask) " )
329
+ assert ( prefix. first ( where: { $0. isLetter || $0. isNumber } ) == nil || hasConstantPrefix, " You cannot have 'semi constant' prefixes at this point " )
280
330
}
281
331
282
- open override func resignFirstResponder( ) -> Bool {
283
- if text. isEmptyOrTrue && !showsMaskIfEmpty && _showsMask && formatter != nil {
284
- _showsMask = false
285
- setNeedsDisplay ( )
332
+ func setCaretPositionAfterSettingText( currentPosition: Int , rawText: String ? , formattedText: String ? ) {
333
+ var pos = currentPosition
334
+ let textCount = rawText? . count ?? 0
335
+ guard let last = formattedText? . prefix ( pos) . last else { return }
336
+
337
+ if !last. isNumber {
338
+ pos = pos + 1 // не 1, а количество элементов до первой цифры с конца
339
+ }
340
+ if pos < textCount {
341
+ setCursorPosition ( offset: pos)
342
+ } else if let count = formattedText? . count {
343
+ let delta = count - textCount
344
+ if abs ( delta) > 2 {
345
+ DispatchQueue . main. async { // async because it may interfere with setting cursor position initiated by system
346
+ self . setCursorPosition ( offset: pos + delta)
347
+ }
348
+ }
286
349
}
287
- return super. resignFirstResponder ( )
288
350
}
289
351
}
0 commit comments