firstRect(for:) 在 UITextField 中返回错误值

Posted

技术标签:

【中文标题】firstRect(for:) 在 UITextField 中返回错误值【英文标题】:firstRect(for:) returns wrong value in UITextField 【发布时间】:2017-08-27 23:17:23 【问题描述】:

我正在尝试使用语法高亮来标记用户输入的错误。

按照Get X and Y coordinates of a word in UITextView 中的建议,我的代码非常适合 UITextView

但是,我现在正在尝试为 UITextField 做类似的事情并且遇到了麻烦。

UITextField 和 UITextView 都符合 UITextInput 并且定位矩形所需的所有方法都适用于两者。

这是 UITextView 的函数

func findRect(forTextMatching match:String, in textView:UITextView) -> CGRect? 
    if let text = textView.text,
       let range = text.range(of: match)
    
        let offset1 = text.distance(from: text.startIndex, to: range.lowerBound)
        let offset2 = text.distance(from: text.startIndex, to: range.upperBound)

        if let pos1 = textView.position(from: textView.beginningOfDocument, offset: offset1),
        let pos2 = textView.position(from: textView.beginningOfDocument, offset: offset2)
        
            if let textRange = textView.textRange(from: pos1, to: pos2)
            
                textView.selectedTextRange = textRange
                let rect = textView.firstRect(for: textRange)
                return rect
            
        
    

    return nil

在测试应用程序中运行它并突出显示返回的矩形,如下所示:

现在尝试用 UITextField 做同样的事情:

func findRect(forTextMatching match:String, in textField:UITextField) -> CGRect? 
    if let text = textField.text,
       let range = text.range(of: match)
    
        let offset1 = text.distance(from: text.startIndex, to: range.lowerBound)
        let offset2 = text.distance(from: text.startIndex, to: range.upperBound)

        // beginningOfDocument is nil unless textField is first responder
        textField.becomeFirstResponder()
        defer 
            textField.resignFirstResponder()
        

        if let pos1 = textField.position(from: textField.beginningOfDocument, offset: offset1),
            let pos2 = textField.position(from: textField.beginningOfDocument, offset: offset2)
        
            if let textRange = textField.textRange(from: pos1, to: pos2)
            
                textField.selectedTextRange = textRange
                var rect = textField.firstRect(for: textRange)

                return rect
            
        
    

    return nil

第一个问题是 UITextField.beginningOfDocument 为 nil,除非该字段是第一响应者。这很容易处理,因此额外的行变成了 becomeFirstResonder 和 resignFirstResponder。

在测试应用程序中运行它并突出显示返回的矩形,如下所示:

返回的矩形略有偏移。

【问题讨论】:

【参考方案1】:

这似乎是 UITextField 的一个错误。在调试中检查视图层次结构,在 UITextField 下有一个额外的视图,类型为 UIFieldEditor,具有框架原点 (7,4)。返回的 rect 似乎与此视图相关,而不是 UITextField。

原来你可以作为 UITextField 的 editRect 来访问这个框架。

UITextField 的修改函数:

func findRect(forTextMatching match:String, in textField:UITextField) -> CGRect? 
    if let text = textField.text,
       let range = text.range(of: match)
    
        let offset1 = text.distance(from: text.startIndex, to: range.lowerBound)
        let offset2 = text.distance(from: text.startIndex, to: range.upperBound)

        // beginningOfDocument is nil unless textField is first responder
        textField.becomeFirstResponder()
        defer 
            textField.resignFirstResponder()
        

        if let pos1 = textField.position(from: textField.beginningOfDocument, offset: offset1),
            let pos2 = textField.position(from: textField.beginningOfDocument, offset: offset2)
        
            if let textRange = textField.textRange(from: pos1, to: pos2)
            
                var rect = textField.firstRect(for: textRange)

                // firstRect is actually relative to editingRect, not textField
                let top = textField.editingRect(forBounds: textField.bounds).origin.y
                let left = textField.editingRect(forBounds: textField.bounds).origin.x
                rect = CGRect(x: rect.origin.x+left, y: rect.origin.y+top, width: rect.width, height: rect.height)

                return rect
            
        
    

    return nil

产生正确的结果:

【讨论】:

一定要归档雷达。 ¯_(ツ)_/¯

以上是关于firstRect(for:) 在 UITextField 中返回错误值的主要内容,如果未能解决你的问题,请参考以下文章

touchesBegan 不触发

使用 uitextfields 创建 uitableview 表单

UITextField右对齐iOS 7

textFieldShouldBeginEditing 被错误的文本字段触发

如何禁用 iOS 8 表情符号键盘?

在 Swift 的弹出式 datePickerView 中添加完成按钮?