@@ -13,6 +13,16 @@ import UIKit
13
13
}
14
14
15
15
open class FormattedTextField : UITextField {
16
+ public typealias Delegate = FormattedTextFieldDelegate
17
+
18
+ public enum PlaceholderMode {
19
+ case whileEmpty
20
+ case always
21
+ }
22
+
23
+ deinit {
24
+ removeTarget ( self , action: #selector( self . textViewEditingChanged ( _: ) ) , for: . editingChanged)
25
+ }
16
26
17
27
public override init ( frame: CGRect ) {
18
28
placeholderLabel = UILabel ( )
@@ -47,16 +57,15 @@ open class FormattedTextField: UITextField {
47
57
} else if let placeholder = super. placeholder {
48
58
placeholderLabel. text = placeholder
49
59
}
50
- // iOS 11: placeholderRect(forBounds:) returns the empty rect when the placeholder is empty
51
- super . placeholder = " "
52
- if #available ( iOS 11 , * ) {
60
+ addSubview ( placeholderLabel )
61
+
62
+ addTarget ( self , action : #selector ( self . textViewEditingChanged ( _ : ) ) , for : . editingChanged )
53
63
64
+ if #available( iOS 11 , * ) {
54
65
if smartInsertDeleteType != . no {
55
66
print ( " [FormattedTextField] warning: smartInsertDeleteType is unsupported " ) ;
56
67
}
57
68
}
58
-
59
- addSubview ( placeholderLabel)
60
69
}
61
70
62
71
open var textFormatter : TextFromatter ? {
@@ -106,7 +115,6 @@ open class FormattedTextField: UITextField {
106
115
} else {
107
116
text = nil
108
117
}
109
- placeholderLabel. isHidden = !formattedText. isEmpty
110
118
}
111
119
}
112
120
@@ -119,12 +127,19 @@ open class FormattedTextField: UITextField {
119
127
}
120
128
}
121
129
130
+ open var placeholderMode : PlaceholderMode = . whileEmpty {
131
+ didSet {
132
+ setNeedsLayout ( )
133
+ }
134
+ }
135
+
122
136
open override var placeholder : String ? {
123
137
get {
124
138
return placeholderLabel. text
125
139
}
126
140
set ( value) {
127
141
placeholderLabel. text = value
142
+ setNeedsLayout ( )
128
143
}
129
144
}
130
145
@@ -147,6 +162,16 @@ open class FormattedTextField: UITextField {
147
162
}
148
163
}
149
164
165
+ open override var textAlignment : NSTextAlignment {
166
+ get {
167
+ return super. textAlignment
168
+ }
169
+ set {
170
+ super. textAlignment = newValue
171
+ setNeedsLayout ( )
172
+ }
173
+ }
174
+
150
175
open override var delegate : UITextFieldDelegate ? {
151
176
get {
152
177
return delegateProxy
@@ -159,20 +184,96 @@ open class FormattedTextField: UITextField {
159
184
open override func layoutSubviews( ) {
160
185
super. layoutSubviews ( )
161
186
162
- var placeholderFrame = self . placeholderRect ( forBounds: bounds)
163
- if let text = self . text {
164
- var attributes : [ NSAttributedString . Key : Any ] ? = nil
165
- if let placeholderFont = font {
166
- attributes = [ . font: placeholderFont]
187
+ layoutPlaceholder ( )
188
+ }
189
+
190
+ open override func editingRect( forBounds bounds: CGRect ) -> CGRect {
191
+ return super. editingRect ( forBounds: bounds) . inset ( by: textRectInset)
192
+ }
193
+
194
+ open override func textRect( forBounds bounds: CGRect ) -> CGRect {
195
+ return super. textRect ( forBounds: bounds) . inset ( by: textRectInset)
196
+ }
197
+
198
+ // MARK: - Private
199
+
200
+ private var textRectInset : UIEdgeInsets {
201
+ return isPlaceholderVisible ? UIEdgeInsets ( top: 0 , left: 0 , bottom: 0 , right: placeholderLabelWidth) : . zero
202
+ }
203
+
204
+ @objc private func textViewEditingChanged( _ sender: AnyObject ? ) {
205
+ layoutPlaceholder ( )
206
+ }
207
+
208
+ private func layoutPlaceholder( ) {
209
+ placeholderLabel. frame = placeholderFrame
210
+ }
211
+
212
+ private var placeholderFrame : CGRect {
213
+ if !isPlaceholderVisible {
214
+ return . zero
215
+ }
216
+
217
+ let textRect = isEditing ? editingRect ( forBounds: bounds) : self . textRect ( forBounds: bounds)
218
+
219
+ var placeholderLabelFrame = textRect
220
+ placeholderLabelFrame. size. width = placeholderLabelWidth
221
+
222
+ switch textAlignment {
223
+ case . center:
224
+ placeholderLabelFrame. origin. x = textRect. midX + enteredTextWidth * 0.5
225
+ case . left, . justified:
226
+ fallthrough
227
+ case . natural where UIView . userInterfaceLayoutDirection ( for: semanticContentAttribute) == . leftToRight:
228
+ placeholderLabelFrame. origin. x += enteredTextWidth
229
+ case . right:
230
+ placeholderLabelFrame. origin. x = textRect. maxX
231
+ default :
232
+ // TODO: Add support for right-to-left direction
233
+ placeholderLabelFrame = . zero
234
+ }
235
+ return placeholderLabelFrame
236
+ }
237
+
238
+ private var isPlaceholderVisible : Bool {
239
+ if placeholder? . isEmpty ?? true {
240
+ return false
241
+ }
242
+
243
+ // Hides placeholder before text field adds scrolling text
244
+ var isVisible = ( placeholderAndTextRect. width - enteredTextWidth - placeholderHiddingGap >= placeholderLabelWidth)
245
+ if isVisible {
246
+ switch placeholderMode {
247
+ case . always:
248
+ isVisible = true
249
+ case . whileEmpty:
250
+ isVisible = unformattedText? . isEmpty ?? true
167
251
}
168
- let prefixWidth = ( text as NSString ) . size ( withAttributes: attributes) . width
169
- placeholderFrame. origin. x += prefixWidth
170
- placeholderFrame. size. width -= prefixWidth
171
252
}
172
- self . placeholderLabel . frame = placeholderFrame;
253
+ return isVisible
173
254
}
174
255
175
- // MARK: - Private
256
+ private var placeholderAndTextRect : CGRect {
257
+ return isEditing ? super. editingRect ( forBounds: bounds) : super. textRect ( forBounds: bounds)
258
+ }
259
+
260
+ // UITextFields adds scrolling before entered text fills all available width
261
+ private var placeholderHiddingGap : CGFloat = 10
262
+
263
+ private var enteredTextWidth : CGFloat {
264
+ guard let text = self . text else {
265
+ return 0
266
+ }
267
+ var attributes : [ NSAttributedString . Key : Any ] ? = nil
268
+ if let placeholderFont = font {
269
+ attributes = [ . font: placeholderFont]
270
+ }
271
+ return ( text as NSString ) . size ( withAttributes: attributes) . width
272
+ }
273
+
274
+ private var placeholderLabelWidth : CGFloat {
275
+ return placeholderLabel. sizeThatFits ( CGSize ( width: CGFloat . infinity, height: CGFloat . infinity) ) . width
276
+ }
176
277
177
278
private let placeholderLabel : UILabel
178
279
@@ -208,7 +309,7 @@ open class FormattedTextField: UITextField {
208
309
unformattedRange = unformattedText. index ( before: unformattedRange. lowerBound) ..< unformattedRange. upperBound
209
310
}
210
311
211
- if let originDelegate = ( delegateProxy. delegate as? FormattedTextFieldDelegate ) ,
312
+ if let originDelegate = ( delegateProxy. delegate as? Delegate ) ,
212
313
originDelegate. responds ( to: #selector( FormattedTextFieldDelegate . textField ( _: shouldChangeUnformattedText: in: replacementString: ) ) ) {
213
314
guard let utf16UnformattedRange = unformattedText. utf16Nsrange ( fromRange: unformattedRange) else {
214
315
return false
@@ -233,8 +334,6 @@ open class FormattedTextField: UITextField {
233
334
self . text = formattedText
234
335
selectedCharactersRange = formattedRange. upperBound..< formattedRange. upperBound
235
336
236
- placeholderLabel. isHidden = !newUnformattedText. isEmpty
237
-
238
337
sendActions ( for: . editingChanged)
239
338
240
339
return false
0 commit comments