CTRunGetImageBounds returns iOS 10 及以下的 x 坐标不正确

CTRunGetImageBounds returns incorrect x coordinate on iOS 10 and below

我正在使用 CoreText 并尝试在 iOS 11/12 上为自定义标签添加 link 支持,但是我也需要支持旧的软件版本。为此,我检查用户的触摸是否与 link 文本边界相交。

为了获得边界,我只需调用 CTRunGetImageBounds(run, context, CFRangeMake(0, 0))。这在 iOS 11 和 12 上完美运行,但在 9 和 10 上坏了。我在 iOS 11 上沿着 (250,8.1,100,30) 的线返回了一个矩形,但在 [=23 上=]9 然而,同样的函数调用returns(0.1,8.1,100,30)。 x 坐标似乎是相对于 运行 而不是实际框架。由于坐标无效,无法在 iOS 9/10 上正确单击 links,这是一个显而易见的问题。

我附上了一张比较两者的图片。预期的行为是 links 应该用绿色矩形突出显示。请注意,iOS 12 模拟器正确执行此操作,而 9.3 由于 x 坐标接近零,所有矩形都向左猛击。

这似乎是 CoreText 错误或 Apple 在没有记录的情况下更改的某些行为。在 iOS 9 CTRunGetImageBounds returns 上,相对于自身的 x 值,而不是更大的框架。这意味着虽然高度和宽度正确,但返回的 x 坐标根本没用。由于这是一个早已失效的固件,它不会得到修复,但我还是找到了解决方法!

Swift 4

/// Get the CoreText relative frame for a given CTRun. This method works around an iOS <=10 CoreText bug in CTRunGetImageBounds
///
/// - Parameters:
///   - run: The run
///   - context: Context, used by CTRunGetImageBounds
/// - Returns: A tight fitting, CT rect that fits around the run
func getCTRectFor(run:CTRun,context:CGContext) -> CGRect {
    let imageBounds = CTRunGetImageBounds(run, context, CFRangeMake(0, 0))
    if #available(iOS 11.0, *) {
        //Non-bugged iOS, can assume the bounds are correct
        return imageBounds
    } else {
        //<=iOS 10 has a bug with getting the frame of a run where it gives invalid x positions
        //The CTRunGetPositionsPtr however works as expected and returns the correct position. We can take that value and substitute it
        let runPositionsPointer = CTRunGetPositionsPtr(run)
        if let runPosition = runPositionsPointer?.pointee {
            return CGRect.init(x: runPosition.x, y: imageBounds.origin.y, width: imageBounds.width, height: imageBounds.height)
        }else {
            //FAILED TO OBTAIN RUN ORIGIN? FALL BACK.
            return imageBounds
        }
    }
}

虽然 CTRunGetImageBounds returns 一个无效的 x 坐标,我们可以使用 CTRunGetPositionsPtr 来修复它,因为它 returns 正确的 origin 为 运行。为了获得范围的完整、正确的框架,我们只需要用我们的新值替换 CTRunGetImageBounds 中的 x 值。

这可能会慢一点,但只会慢几分之一毫秒。