Я столкнулся с проблемой в моей заявке.Я хочу иметь возможность подключиться к подпредставлению и определить, какой из слоев подпредставления был прослушан.Я (наивно?) Решил использовать метод hitTest (: CGPoint) корневого слоя подпредставления.Результаты оказались не такими, как я ожидал.Я создал игровую площадку, чтобы проиллюстрировать сложность.
Игровая площадка (код ниже):
- Создает контроллер представления и его корневое представление
- Добавляет подпредставление вкорневой вид
- Добавляет подслой к корневому слою подпредставления
Вот как это выглядит:
- Корневой слой корневого представления черный
- Корневой слой подпредставления красный
- Подслой подпредставления полупрозрачный серый
- Белая линия предназначена дляиллюстративные цели
Когда игровая площадка работает, я начинаю постукивать, начиная с верхней части белой линии и спускаясь вниз.Вот операторы печати, полученные с помощью метода обработки жестов касания:
Root view tapped @ (188.5, 12.5)
Тест попадания возвратил «Root View -> Root Layer» с рамкой (0.0,0.0, 375.0, 668.0)
Повернутый вид root повернут @ (188.5, 49.0)
Тест попадания вернул «Root View -> Root Layer» с рамкой (0.0, 0.0, 375.0, 668.0)
Root view tapped @ (188.5, 88.5)
Тест попадания возвратил "Root View -> Root Layer" с рамкой (0.0, 0.0, 375.0, 668.0)
Sub view tapped @ (140.0, 11.0)
дополнительный вид повернут @ (138.5, 32.5)
дополнительный вид повернут @ (138.5, 55.5)
дополнительный вид повернут @ (138.0, 84.0)
Дополнительный вид коснулся @ (139,0, 113,0)
Тест хита вернул «Дополнительный вид -> Корневой слой» с рамкой (50,0, 100,0, 275,0, 468,0)
Дополнительный вид коснулся @ (138,0, 138,5)
Тест попадания возвратил «Sub View -> Root Layer» с рамкой (50.0, 100.0, 275.0, 468.0)
Подвид повернул @ (140.0, 171.5)
Тест попадания вернулся «Sub View-> Root Layer "с фреймом (50,0, 100,0, 275,0, 468,0)
постукивание по вложенному представлению @ (137,5, 206,5)
Тест попадания вернул «Sub View -> Sub Layer» с рамкой (50,0, 100,0, 175,0, 268,0)
Дополнительный вид коснулся @ (135,5, 224,0)
Тест хита вернул «Дополнительный вид -> Дополнительный слой» с рамкой (50,0, 100,0, 175,0, 268,0)
Вы можете видеть, что, когда я нажимаю в корневом представлении, все «нормально».Как только я вхожу в подпредставление, тест попадания больше не находит слой, пока я не окажусь более чем на 100 пунктов ниже вершины подпредставления;в этот момент встречается корневой слой подпредставления.100 баллов после этого встречается подслой корневого слоя подпредставления.
Что происходит?Ну, очевидно, когда подпредставление было добавлено к корневому представлению, корневой слой подпредставления стал подслоем корневого слоя корневого представления, и его рамка была изменена, чтобы отразить его размещение в корневом слое корневого представления, а не в дополнительном представлении.
Итак, можно задать следующие вопросы:
- Правильно ли я понимаю, что, по-видимому, произошло?
- Это нормально и ожидаемо?
- Как мнеиметь дело с этим?
Спасибо, что нашли время, чтобы рассмотреть мой вопрос.
Вот код игровой площадки:
//: [Previous](@previous)
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
class View : UIView {
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(2.0)
context.move(to: CGPoint(x: rect.midX, y: 0))
context.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
context.strokePath()
}
}
private var subview: View!
override func loadView() {
let rootView = View(frame: CGRect.zero)
rootView.backgroundColor = .black
rootView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))))
rootView.layer.name = "Root View -> Root Layer"
self.view = rootView
}
override func viewDidLayoutSubviews() {
subview = View(frame: view.frame.insetBy(dx: 50.0, dy: 100.0))
subview.backgroundColor = .red
subview.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))))
subview.layer.name = "Sub View -> Root Layer"
let sublayer = CALayer()
sublayer.bounds = subview.layer.bounds.insetBy(dx: 50, dy: 100)
sublayer.position = CGPoint(x: subview.layer.bounds.midX, y: subview.layer.bounds.midY)
sublayer.backgroundColor = UIColor.gray.cgColor.copy(alpha: 0.5)
sublayer.name = "Sub View -> Sub Layer"
subview.layer.addSublayer(sublayer)
view.addSubview(subview)
}
@objc func tapGestureRecognized(_ sender: UITapGestureRecognizer) {
guard sender.state == .ended, let tappedView = sender.view else { return }
var viewName: String
switch tappedView {
case view: viewName = "Root"
case subview: viewName = "Sub"
default: return
}
let location = sender.location(in: tappedView)
print("\n\(viewName) view tapped @\(location)")
if let layer = tappedView.layer.hitTest(location) {
print("Hit test returned <\(layer.name ?? "unknown")> with frame \(layer.frame)")
}
}
}
PlaygroundPage.current.liveView = ViewController()
PlaygroundPage.current.needsIndefiniteExecution = true
//: [Next](@next)
Обновление:Решенная проблема
Под руководством всегда знающего Мэтта Нюбурга (и его самой превосходной книги) я изменил метод обработчика жестов касания следующим образом:
@objc func tapGestureRecognized(_ sender: UITapGestureRecognizer) {
guard sender.state == .ended, let tappedView = sender.view else { return }
var viewName: String
let hitTestLocation: CGPoint
switch tappedView {
case view:
viewName = "Root"
hitTestLocation = sender.location(in: tappedView)
case subview:
viewName = "Sub"
hitTestLocation = sender.location(in: tappedView.superview!)
default: return
}
print("\n\(viewName) view tapped @\(sender.location(in: tappedView))")
if let layer = tappedView.layer.hitTest(hitTestLocation) {
print("Hit test returned <\(layer.name ?? "unknown")> with frame \(layer.frame)")
}
}
Теперь консольвывод выглядит хорошо:
Root View повернут @ (186.0, 19.0)
Тест попадания вернул «Root View -> Root Layer» с рамкой (0.0, 0.0, 375.0, 668.0)
Root view tapped @ (187.0, 45.0)
Проверка тестаповернул «Root View -> Root Layer» с рамкой (0.0, 0.0, 375.0, 668.0)
Root View повернул @ (187.0, 84.5)
Тест хита вернул «Root View -> Root Layer» скадр (0,0, 0,0, 375,0, 668,0)
Sub view tapped @ (138.0, 9.0)
Тест попадания вернул «Sub View -> Root Layer» с рамкой (50.0, 100.0, 275.0, 468.0)
Sub view tapped @ (137.5,43.5)
Тест попадания возвратил «Sub View -> Root Layer» с рамкой (50.0, 100.0, 275.0, 468.0)
Подвид коснулся @ (138.5, 77.5)
Возвращен тест попадания «SubВид -> Root Layer "с рамкой (50.0, 100.0, 275.0, 468.0)
Подвид повернутым @ (138.0, 111.0)
Тест попадания вернул" Sub View -> Sub Layer 1 "с рамкой (50,0, 100,0, 175,0, 268,0)
При просмотре вспомогательного вида коснулся @ (138,5, 140,5)
Тест попадания возвратил «Подвид -> Подуровень 1» с рамкой (50,0, 100,0, 175,0, 268,0)
Подвид, коснувшийся @ (139.5, 174.5)
Тест попадания вернул «Подвид -> Подуровень 1» с рамкой (50.0, 100.0, 175.0, 268.0)
ВторойОбновление - более четкий код
Я возвращаюсь назад с кодом, который в итоге использовал для обнаружения касаний подуровней.Надеюсь, он более понятен, чем приведенный выше код игровой площадки.
private struct LayerTouched : CustomStringConvertible {
let view: UIView // The touched view
var layer: CALayer // The touched layer
var location: CGPoint // The touch location expressed in the touched layer's coordinate system
init(by recognizer: UIGestureRecognizer) {
view = recognizer.view!
let gestureLocation = recognizer.location(in: view)
// AFAIK - Any touchable layer will have a super layer. Hence the forced unwrap is okay.
let hitTestLocation = view.layer.superlayer!.convert(gestureLocation, from: view.layer)
layer = view.layer.hitTest(hitTestLocation)!
location = layer.convert(gestureLocation, from: view.layer)
}
var description: String {
return """
Touched \(layer)
of \(view)
at \(location).
"""
}
}