UITextField在开始编辑时文本会稍微向下跳

21

我这里有一个奇怪的问题。我的表格单元格中有UITextFields。当我选择该字段时,文字会往下轻微跳动:

输入图像描述

字体为系统默认17号。我已经打开了17号大小的自适应,关闭后仍会出现跳动。我尝试使用不同的边框样式,但也没有任何区别。我尝试关闭剪切到边界功能,但它仍然跳动。我还尝试将框架变得更高(非常高),它仍然跳动。唯一有效的方法是将字体大小缩小到13号。我在做错了什么吗?如果我可以通过减小字体大小来解决跳动问题,那么增大框架尺寸为什么不起作用呢?希望能够得到任何关于此的指针。谢谢!


1
我认为这是文本框高度的问题。请增加一些文本框的高度,然后再检查一下。 - Gurjinder Singh
你是在使用库吗?还是默认的 iPhone 键盘?@KexAri - Jitendra Modi
你能发给我你的演示吗?因为我没有遇到那样的问题。我为此构建了一个新的演示,一切都正常工作。请检查一下这不是Xcode版本的问题。你正在使用哪个Xcode版本? - Jitendra Modi
@JeckyModi 这些是没有使用库的UITextFields。我所使用的版本是iOS 10。 - David
1
你能分享一个可以重现它的示例项目吗? - Kamil.S
显示剩余3条评论
4个回答

11

我必须承认,我在开发中一直看到这个小弹跳,但从未对它进行过调查。以下是我用来复制您的问题的一些要点:

  1. 两个文本字段,默认尺寸和设置
  2. 使用系统默认字体,并将其大小从13更改为25,以测试其行为

我想可能有更多其他方法来解决您的问题,但我没有找到一个快速的属性来停止这个小的文字“弹跳”。 我决定分析当前的UITextField$ xcrun swift -version = Swift 3.1)以查看其组成:

import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var myTextField: UITextField!
    override func viewDidLoad() {
        super.viewDidLoad()
        myTextField.delegate = self
    }
    func textFieldDidBeginEditing(_ textField: UITextField) {
        print("\ntextFieldDidBeginEditing")
        showLayersDescription()
    }
    func textFieldDidEndEditing(_ textField: UITextField) {
        print("\ntextFieldDidEndEditing")
        showLayersDescription()
    }
    func showLayersDescription() {
        myTextField.layer.sublayers?.forEach{ print($0.debugDescription)}
    }
}

其中myTextField实际上是两个文本字段中的一个。

使用字体大小17的输出

enter image description here

主要似乎有3个CALayer

  • 一个帧为CGRect (0 0; 252 30)的图层,它的代理是_UITextFieldRoundedRectBackgroundViewNeue,它的尺寸与我们的文本字段相同
  • 一个帧为CGRect (7 2; 238 26)的图层,仅在我们的textFieldDidBeginEditing中显示,其代理为UIFieldEditor,看起来负责编辑部分..
  • 一层仅在textFieldDidEndEditing中出现,帧为CGRect.zero,并带有代理UITextFieldLabel

如果我向第一个文本字段写入内容,然后转到下一个文本字段,则不会发生任何事情。但是,如果我返回第一个文本字段,然后转到第二个文本字段,具有和CGRect.zero相等尺寸的图层会更改为CGRect(7 0.5; 238 27.5)

这是我们在编辑开始和结束时看到的小高度(0.5)

我们可以尝试使用扩展来优化调试并拦截这些图层:

extension UITextField
{
    func debugLayers() {
        print("We have the follow layers:")
        let FieldEditor: AnyObject.Type = NSClassFromString("UIFieldEditor")!
        let LabelLayer: AnyObject.Type = NSClassFromString("_UILabelLayer")!
        let TextFieldRoundRect: AnyObject.Type = NSClassFromString("_UITextFieldRoundedRectBackgroundViewNeue")!
        self.layer.sublayers?.forEach{
            if ($0.delegate?.self.isKind(of: TextFieldRoundRect))! { print("- layer with _UITextFieldRoundedRectBackgroundViewNeue as delegate have frame:\($0.frame)") }
            if ($0.delegate?.self.isKind(of: FieldEditor))! { print("- layer with UIFieldEditor as delegate have frame:\($0.frame)") }
            if $0.self.isKind(of: LabelLayer) { print("- layer is kind of _UILabelLayer have frame:\($0.frame)") }
        }
    }
}

所以我们举个例子:

import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var myTextField: UITextField!
    override func viewDidLoad() {
        super.viewDidLoad()
        myTextField.delegate = self
    }
    func textFieldDidBeginEditing(_ textField: UITextField) {
        print("\ntextFieldDidBeginEditing")
        myTextField.debugLayers()
    }
    func textFieldDidEndEditing(_ textField: UITextField) {
        print("\ntextFieldDidEndEditing")
        myTextField.debugLayers()
    }
}

始终使用17号字体输出:

enter image description here

正如我们所看到的,在该层中存在0.5个高度差异

通过其他尝试,我发现这种行为仅在默认大小介于13和17之间时发生,在13以下和18到25之间则没有,正如您在问题中所述。

一种解决方案:

我认为截取并尝试纠正这种情况的最佳方法是创建一个新的扩展程序:

extension UITextField
{
    override open func layoutSubviews() {
        super.layoutSubviews()
        let FieldEditor: AnyObject.Type = NSClassFromString("UIFieldEditor")!
        let LabelLayer: AnyObject.Type = NSClassFromString("_UILabelLayer")!
        self.layer.sublayers?.forEach{
            if ($0.delegate?.self.isKind(of: FieldEditor))! {
                var f = $0.frame
                f.origin.y = 0.0
                $0.frame = f
            }
            if $0.self.isKind(of: LabelLayer) {
                var layerFrame = CGRect.zero
                layerFrame.origin = self.editingRect(forBounds: self.bounds).origin
                layerFrame.size = self.editingRect(forBounds: self.bounds).size
                if let size = self.font?.pointSize, 14 ... 17 ~= size {
                    layerFrame.origin.y = -0.5
                } else {
                    layerFrame.origin.y = 0.0
                }
                $0.frame = layerFrame
            }
        }
    }
} 

最终考虑:

此扩展可防止在切换到另一个textField时文本出现小的“跳动”。这可能是为了平衡:

contentsCenter = CGRect (0.485 0.485; 0.000588235 0.000588235)

第二和第三层与该字体大小组相关,如您在扩展中所见,我将带有 UIFieldEditor 代理的图层高度原点设置为零(之前高度为2.0),以平衡约束差异。

更新有效且已批准的扩展:

我已阅读您的评论,因此我深入分析了图层的情况:我发现高度差为-0.5的图层从未呈现子图层,而另一个则总是呈现一个子图层(其代理为UIFieldEditor),因此我们可以轻松地进行扩展修正:

extension UITextField
{
    override open func layoutSubviews() {
        super.layoutSubviews()
        self.layer.sublayers?.forEach{
            if let subs = $0.sublayers, subs.count>0 {
                var f = $0.frame
                f.origin.y = 0.0
                $0.frame = f
            } else {
                var layerFrame = CGRect.zero
                layerFrame.origin = self.editingRect(forBounds: self.bounds).origin
                layerFrame.size = self.editingRect(forBounds: self.bounds).size
                if let size = self.font?.pointSize, 14 ... 17 ~= size {
                    layerFrame.origin.y = -0.5
                } else {
                    layerFrame.origin.y = 0.0
                }
                $0.frame = layerFrame
            }
        }
    }
}

我已经测试了这个新扩展程序,它的行为与旧的扩展程序一样正确。


这是一个包含代码的 GitHUB 存储库此处,可用于进行测试。 - Alessandro Ornano
我很快就会测试这个解决方案,但是从一个快速的浏览来看,我很好奇这些代码行是否会因为以下内容而被应用商店拒绝: "let FieldEditor: AnyObject.Type = NSClassFromString("UIFieldEditor")! let LabelLayer: AnyObject.Type = NSClassFromString("_UILabelLayer")!" - David
@David 这是一个很好的观察,我已经更新并测试了扩展,看一下我的回答末尾。 - Alessandro Ornano

3

所以您可能会遇到一些非常奇怪的问题。如果您将UITextField子类化为以下内容。

class NonJumpyTextField: UITextField {

    override func textRect(forBounds bounds: CGRect) -> CGRect {
        var previousRect = super.textRect(forBounds: bounds)

        print("Bounds: \(bounds)")
        print("TextRect: \(previousRect)")
        print("TextFieldRect \(frame)")

        //not reccomended but does seem to help
        previousRect.origin.y = 2.25

        return previousRect
    }
}

当编辑时,读取日志会显示一些奇怪的行为。

Bounds: (0.0, 0.0, 100.0, 100.0)
TextRect: (7.0, 2.0, 86.0, 96.0)
TextFieldRect (64.0, 66.0, 97.0, 30.0)
Bounds: (0.0, 0.0, 97.0, 30.0)
TextRect: (7.0, 2.0, 83.0, 26.0)
TextFieldRect (64.0, 66.0, 97.0, 30.0)
Bounds: (0.0, 0.0, 100.0, 100.0)
TextRect: (7.0, 2.0, 86.0, 96.0)
TextFieldRect (64.0, 66.0, 97.0, 30.0)

编辑完成后

Bounds: (0.0, 0.0, 97.0, 30.0)
TextRect: (7.0, 2.0, 83.0, 26.0)
TextFieldRect (64.0, 66.0, 97.0, 30.0)
Bounds: (0.0, 0.0, 100.0, 100.0)
TextRect: (7.0, 2.0, 86.0, 96.0)
TextFieldRect (64.0, 66.0, 97.0, 30.0)

在我处理的示例中,强制暴力破解y位置确实有所帮助,但并不推荐。我怀疑问题与边界似乎是随机的100 x 100有关。希望这里的内容能帮助您找到适合您的解决方案。

2

改变UITextField的contentVerticalAlignment属性可能会导致奇怪的行为。

您可以添加一个符号断点

[UITextField setContentVerticalAlignment:]

来检查是否意外更改了UITextField的contentVerticalAlignment属性。


-2
你可以尝试增加UITextField中inputView的高度。
当对象成为第一响应者时,UITextField会呈现inputView。

1
我该如何增加它? - KexAri

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