是否可以在 SwiftUI 中为 TextField 添加字距调整?

Posted

技术标签:

【中文标题】是否可以在 SwiftUI 中为 TextField 添加字距调整?【英文标题】:Is it possible to add kerning to a TextField in SwiftUI? 【发布时间】:2019-11-05 15:13:49 【问题描述】:

要匹配样式指南,我必须向文本字段添加字距调整,包括占位符和值本身。

借助 UIKit,我能够做到这一点:

    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
        ])
    

在 SwiftUI 中,我发现我可以对 Text 元素应用字距调整效果,如下所示:

    Text("My label with kerning")
       .kerning(0.7)

不幸的是,我找不到将字距调整样式应用于 TextField 的值或占位符的方法。关于这个有什么想法吗?提前致谢

【问题讨论】:

如果您需要 UIKit 视图,请使用 UIViewRepresentable。 @matt 我已经在 SwiftUI 中实现了视图,并希望坚持下去。在 SwiftUI 视图inside 中只使用唯一的 UITextField 子类是不可能的,是吗? 是的;这正是你可以做的,使用 UIViewRepresentable。 SwiftUI 视图可以存在于 UIViews 中,而 UIViews 可以存在于 SwiftUI 视图中。 【参考方案1】:

HackingwithSwift 上有一个简单的教程,展示了如何实现 UITextView。它可以很容易地适应 UITextField。

这是一个简单的示例,展示如何为您使用UIViewRepresentable UITextField。在文本和占位符上设置字距。

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
        ])
    


更新

以上方法不适用于在属性文本上设置字距。借鉴 Costantino Pistagna 在他的medium article 中所做的出色工作,我们需要做更多的工作。

首先,我们需要创建一个 UITextField 的包装版本,它允许我们访问委托方法。

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?()
    

由于每次文本更改时都会调用shouldChangeCharactersIn 委托方法,我们应该使用它来更新attributedText 值。我尝试先使用proposedVale,但它会使字符加倍,如果我们使用currentValue,它会按预期工作

现在我们可以在UIViewRepresentable 中使用WrappedTextField

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)
    

我们在makeUIView 中为占位符设置了属性文本。占位符文本没有被更新,所以我们不需要担心改变它。

这是我们如何使用它的:

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
        
    

【讨论】:

嗯……实际上,它工作得很好,但我无法让内部填充工作……This answer 过去对我来说工作得很好:( :) 我很高兴字距调整现在为您工作。如果您有一个与字距调整无关的问题,那么我建议您将其写成一个新问题,以便您可以清楚地表达您需要做的所有事情,因为 SO 上的 cmets 并不允许进行详细的讨论。一方面,这里的代码格式不好;其次,你被限制在 600 个字符以内,所以很难充分表达自己。

以上是关于是否可以在 SwiftUI 中为 TextField 添加字距调整?的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 如何获取对 List 内编辑 Textfield 的参考?

如何在 SwiftUI 中为 Start->Max->Start 设置动画?

是否可以使用 SwiftUI 将键盘上的“返回”键更改为“完成”?

SwiftUI:

SwiftUI:TextField文本输入

如何在 SwiftUI 中为列表创建标题?