Reputation: 1063
To match a Styleguide I have to add kerning to a textfield, both placeholder and value itself.
With UIKit I was able to do so with:
class MyTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
// ...
self.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "", attributes: [
//...
NSAttributedString.Key.kern: 0.3
])
self.attributedText = NSAttributedString(string: "", attributes: [
// ...
NSAttributedString.Key.kern: 0.3
])
}
}
In SwiftUI, I could figure out that I could apply a kerning effect to a Text
element like so:
Text("My label with kerning")
.kerning(0.7)
Unfortunately, I could not find a way to apply a kerning style to neither a TextField's value nor placeholder. Any ideas on this one? Thanks in advance
Upvotes: 6
Views: 3320
Reputation: 28539
There is a simple tutorial on HackingwithSwift that shows how to implement a UITextView. It can easily be adapted for UITextField.
Here is a quick example showing how to use UIViewRepresentable
for you UITextField
. Setting the kerning on both the text and the placeholder.
struct ContentView: View {
@State var text = ""
var body: some View {
MyTextField(text: $text, placeholder: "Placeholder")
}
}
struct MyTextField: UIViewRepresentable {
@Binding var text: String
var placeholder: String
func makeUIView(context: Context) -> UITextField {
return UITextField()
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.attributedPlaceholder = NSAttributedString(string: self.placeholder, attributes: [
NSAttributedString.Key.kern: 0.3
])
uiView.attributedText = NSAttributedString(string: self.text, attributes: [
NSAttributedString.Key.kern: 0.3
])
}
}
The above doesn't work for setting the kerning on the attributedText. Borrowing from the fantastic work done by Costantino Pistagna in his medium article we need to do a little more work.
Firstly we need to create a wrapped version of the UITextField
that allows us access to the delegate methods.
class WrappableTextField: UITextField, UITextFieldDelegate {
var textFieldChangedHandler: ((String)->Void)?
var onCommitHandler: (()->Void)?
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let nextField = textField.superview?.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
return false
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let currentValue = textField.text as NSString? {
let proposedValue = currentValue.replacingCharacters(in: range, with: string)
print(proposedValue)
self.attributedText = NSAttributedString(string: currentValue as String, attributes: [
NSAttributedString.Key.kern: 10
])
textFieldChangedHandler?(proposedValue as String)
}
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
onCommitHandler?()
}
}
As the shouldChangeCharactersIn
delegate method will get called every time the text changes, we should use that to update the attributedText
value. I tried using first the proposedVale
but it would double up the characters, it works as expected if we use the currentValue
Now we can use the WrappedTextField
in the UIViewRepresentable
.
struct SATextField: UIViewRepresentable {
private let tmpView = WrappableTextField()
//var exposed to SwiftUI object init
var tag:Int = 0
var placeholder:String?
var changeHandler:((String)->Void)?
var onCommitHandler:(()->Void)?
func makeUIView(context: UIViewRepresentableContext<SATextField>) -> WrappableTextField {
tmpView.tag = tag
tmpView.delegate = tmpView
tmpView.placeholder = placeholder
tmpView.attributedPlaceholder = NSAttributedString(string: self.placeholder ?? "", attributes: [
NSAttributedString.Key.kern: 10
])
tmpView.onCommitHandler = onCommitHandler
tmpView.textFieldChangedHandler = changeHandler
return tmpView
}
func updateUIView(_ uiView: WrappableTextField, context: UIViewRepresentableContext<SATextField>) {
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentHuggingPriority(.defaultLow, for: .horizontal)
}
}
We set the attributed text for the placeholder in the makeUIView
. The placeholder text is not being updated so we don't need to worry about changing that.
And here is how we use it:
struct ContentView: View {
@State var text = ""
var body: some View {
SATextField(tag: 0, placeholder: "Placeholder", changeHandler: { (newText) in
self.text = newText
}) {
// do something when the editing of this textfield ends
}
}
}
Upvotes: 5