在SwiftUI中,是否可以为TextField添加字符间距(kerning)?

4
为了匹配样式指南,我需要在文本字段(占位符和值本身)中添加字距。
使用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中,我发现可以通过以下方式将字距效果应用于

1
如果您需要一个UIKit视图,请使用UIViewRepresentable。 - matt
@matt,我已经在SwiftUI中实现了该视图,并希望坚持使用它。在SwiftUI视图中仅使用一个UITextField子类是不可能的,对吗? - emmics
没错,你可以使用UIViewRepresentable来实现这一点。SwiftUI视图可以存在于UIView中,而UIView也可以存在于SwiftUI视图中。 - Rob Napier
1个回答

5

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

更新

上述方法不能用于设置attributeText的字距。借鉴Costantino Pistagna在他的博客文章中做出的杰出工作,我们需要做更多的工作。

首先,我们需要创建一个包装版本的 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”值。我先尝试使用“proposedValue”,但会重复出现字符,如果使用“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
        }
    }
}

其实,它运行得很好,但我无法让内部填充起作用...这个答案曾经对我有用 :( 此答案 - emmics
1
我很高兴你现在解决了字距调整的问题。如果你有与字距调整无关的问题,我建议你将其写成一个新问题,这样你可以清楚地表达所有需要做的事情,因为SO上的评论并不真正允许进行详细的讨论。首先,代码在这里格式化得不好;其次,你只能使用600个字符,所以很难完全表达自己。 - Andrew
我使用了这个解决方案,但是在将颜色设置为黑色时存在问题。当前字符直到下一个字符被按下才会改变颜色。 你有什么解决方案吗? - Roman Sterlin
@RomanSterlin 我能够轻松更改文本的颜色。如果您遇到困难,我建议您撰写一个新问题,说明您尝试过什么以及哪些方面不起作用。不幸的是,评论并不适合或鼓励进行长时间的讨论。 - Andrew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接