안녕金宇成!欢迎来到论坛。不幸的是,简短的回答是否。没有 api 可以在特定的点击位置查询单词,甚至没有使用UILabel(或CATextLayer)呈现内容。
更长的答案是:如果您真的想这样做,有一些选择,但我不会将其用于生产。在显示字幕时检查AVPlayerViewController 的视图层次结构将显示字幕是在FigFCRCALayerOutputNodeLayer 中呈现的。在运行 iOS 14.4 的模拟器上,该层位于
avPlayerVc.view.subviews.first?.subviews.first?.subviews.first?.layer.sublayers?.first?.sublayers?[1].sublayers?.first?.sublayers?.first?.sublayers?.first(这可能会在将来随时更改,甚至可能在此版本以下不起作用)。文字直接设置为图层content(包括圆角半透明背景视图)。
我玩了一会儿 Vision,虽然它有点滞后,但它可以将图层内容 (CGImage) 提供给文本识别请求,然后将结果拆分为单词并检查触摸位置是否在边界内的话。为了获得触摸点,我将AVPlayerViewController 子类化(我知道,这很糟糕,但如果你直接使用AVPlayerLayer 会更容易)并将触摸从touchesBegan 转换为屏幕坐标:
final class PlayerVC: AVPlayerViewController {
var onTap: ((CGPoint) -> Void)?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard let tapLocation = touches.first?.location(in: nil) else { return }
onTap?(tapLocation)
}
}
在该位置获取单词的实际代码如下所示(playerVc 指向 PlayerVC 实例:
func tap(tapLocation: CGPoint) {
guard
let subtitleLayer = playerVc.view.subviews.first?.subviews.first?.subviews.first?.layer.sublayers?.first?.sublayers?[1].sublayers?.first?.sublayers?.first?.sublayers?.first,
CFGetTypeID(subtitleLayer.contents as CFTypeRef) == CGImage.typeID
else { return }
let image = subtitleLayer.contents as! CGImage
let requestHandler = VNImageRequestHandler(cgImage: image)
let recognizeTextHandler: (VNRequest, Error?) -> Void = { request, error in
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
for observation in observations {
guard let topCandidate = observation.topCandidates(1).first, topCandidate.string != "" else { continue }
for word in topCandidate.string.components(separatedBy: " ") {
guard let range = topCandidate.string.range(of: word) else { continue }
if let boundinBox = try? topCandidate.boundingBox(for: range) {
let transform = CGAffineTransform.identity
.scaledBy(x: 1, y: -1)
.translatedBy(x: 0, y: -subtitleLayer.frame.size.height)
.scaledBy(x: subtitleLayer.frame.size.width, y: subtitleLayer.frame.size.height)
let convertedTopLeft = boundinBox.topLeft.applying(transform)
let convertedBottomRight = boundinBox.bottomRight.applying(transform)
let localRect = CGRect(x: convertedTopLeft.x,
y: convertedTopLeft.y,
width: convertedBottomRight.x - convertedTopLeft.x,
height: convertedBottomRight.y - convertedTopLeft.y)
let globalRect = subtitleLayer.convert(localRect, to: nil)
if globalRect.contains(tapLocation) {
print("You tapped \(word)")
}
}
}
}
}
let request = VNRecognizeTextRequest(completionHandler: recognizeTextHandler)
request.usesLanguageCorrection = false
request.recognitionLevel = .accurate
do {
try requestHandler.perform([request])
} catch {
print("Unable to perform the request \(error)")
}
}