Распознавание сенсорных событий только внутри маскированного представления

Я создаю это структурировано через маски:

введите здесь описание изображения

Каждый шестиугольник должен быть кликабельным. Это код, который я использовал:

    // To create one masked hexagun

    let hex = UIImage(named: "hexum")
    let mask = CALayer()
    mask.contents = hexum!.CGImage
    mask.frame = CGRectMake(0, 0, hex!.size.width, hex!.size.height)

    let img = UIImageView(image: UIImage(named: "img"))
    img.layer.mask = mask
    img.layer.masksToBounds = true

    // Gesture Recognizer

    let singleTap = UITapGestureRecognizer(target: self, action: "tapDetected")
    singleTap.numberOfTapsRequired = 1
    img.addGestureRecognizer(singleTap)
    img.userInteractionEnabled = true

    func tapDetected() {
        print("Clicked!")
    }

Проблема в том, что область щелчка больше, чем маска, что вызовет неудобство перекрытия областей друг с другом. Что-то вроде этого:

Это не то, что я хочу!

Желтая рамка показывает интерактивную область (на самом деле она не видна)

Я новичок, это может быть тривиальная проблема, но вы можете помочь мне решить? Спасибо.


person Damasio    schedule 23.10.2015    source источник
comment
Проверьте этот аналогичный вопрос: stackoverflow.com/questions/13291919/   -  person Ty Lertwichaiworawit    schedule 23.10.2015
comment
@tnylee спасибо, посмотрю!   -  person Damasio    schedule 23.10.2015


Ответы (5)


Если вы хотите сделать это идеально, используйте UIGestureRecognizerDelegate метод gestureRecognizer(gesture, shouldReceiveTouch: touch) -> Bool. Вам нужно будет сопоставить данный распознаватель жестов с конкретным шестиугольником, а затем выполнить точную проверку попадания пикселей на изображение для этого шестиугольника. Эта последняя часть достигается путем рендеринга изображения маски в графическом контексте и нахождения пикселя в точке, соответствующей местоположению касания.

Однако это, скорее всего, перебор. Вы можете упростить задачу, тестируя каждую фигуру как круг, а не как шестиугольник. Форма круга примерно соответствует шестиугольнику, поэтому она будет работать почти так же для пользователя и позволяет избежать беспорядочного альфа-равенства на уровне пикселей. Неточность сенсорного ввода скроет неточные области.

Другой вариант — переработать ваши представления, чтобы они основывались на CAShapeLayer масках. CAShapeLayer включает свойство path. Пути Безье в UIKit включают свои собственные свернутые версии методов path-contains-point, поэтому вы можете просто использовать их для этой цели.

person Benjamin Mayo    schedule 23.10.2015

Вы можете принять протокол UIGestureRecognizerDelegate и реализовать метод gestureRecognizer(_:shouldReceiveTouch:) для дальнейшего ограничения того, должен ли срабатывать жест. Ссылка, предложенная @tnylee, была бы хорошей отправной точкой с точки зрения выяснения того, как проводить такое тестирование попаданий.

person Charles A.    schedule 23.10.2015

@Benjamin Mayo предложил отличные варианты решения проблемы. В итоге я выбрал самый простой, но эффективный способ: проверить каждую фигуру на попадание в виде круга.

Я добавляю код, который может помочь кому-то еще:

class hexum: UIImageView {

    var maskFrame: CGRect?

    convenience init(mask: String, inside: String) {

    // Mask things:

        let masked = CALayer()
        let img = UIImage(named: mask)
        masked.contents = img?.CGImage
        masked.frame = CGRectMake(x, y, img!.size.width, img!.size.height)

        self.init(image: UIImage(named: inside))
        self.layer.mask = masked
        self.layer.masksToBounds = true

        maskFrame = masked.frame

    }

    // The touch event things
    // Here, I got help from @Matt in (http://stackoverflow.com/a/21081518/3462518):

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        let p = UIBezierPath(ovalInRect: maskFrame!)
        return p.containsPoint(point)
    }
}

    let firstOne = hexum(mask: "img1", inside: "img2")
    let tap = UITapGestureRecognizer(target: self, action: "clicked")
    tap.numberOfTapsRequired = 1

    firstOne.userInteractionEnabled = true
    firstOne.addGestureRecognizer(tap)

    func clicked() {
    ...
    }

Результат:

введите здесь описание изображения

person Damasio    schedule 25.10.2015

Вот Swift 3 HexagonImageView, доступный только внутри шестиугольника:

Сначала создайте путь UIBezier:

final class HexagonPath: UIBezierPath {
required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

var sideLength: CGFloat = 100 {
    didSet {
        redrawPath()
    }
}

override init() {
    super.init()
    redrawPath()
}

private func redrawPath() {
    removeAllPoints()

    let yDrop = sideLength / 2

    move(to: CGPoint(x: sideLength, y: 0))

    addLine(to: CGPoint(x: sideLength * 2, y: yDrop))
    addLine(to: CGPoint(x: sideLength * 2, y: yDrop + sideLength))

    addLine(to: CGPoint(x: sideLength, y: (yDrop * 2) + sideLength))
    addLine(to: CGPoint(x: 0, y: yDrop + sideLength))

    addLine(to: CGPoint(x: 0, y: yDrop ))
    //addLine(to: CGPoint(x: sideLength, y: 0))
    close()
}

}

Затем создайте Hexagon UIImageView:

class HexagonImageView: UIImageView {
let hexagonPath = HexagonPath()

var sideLength: CGFloat = 100 {
    didSet {
        hexagonPath.sideLength = sideLength
        initilize()
    }
}

init() {
    super.init(frame: CGRect())
    initilize()
}

override init(frame: CGRect) {
    super.init(frame: frame)
    initilize()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    initilize()
}

private func initilize() {
    self.frame.size.width = sideLength * 2
    self.frame.size.height = sideLength * 2

    contentMode = .scaleAspectFill
    mask(withPath: hexagonPath)
}

// MAKE THE TAP-HIT POINT JUST THE INNER PATH
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    return hexagonPath.contains(point)
}

}

Используя это расширение:

extension UIView {
func mask(withRect rect: CGRect, inverse: Bool = false) {
    let path = UIBezierPath(rect: rect)
    let maskLayer = CAShapeLayer()

    if inverse {
        path.append(UIBezierPath(rect: self.bounds))
        maskLayer.fillRule = kCAFillRuleEvenOdd
    }

    maskLayer.path = path.cgPath

    self.layer.mask = maskLayer
}

func mask(withPath path: UIBezierPath, inverse: Bool = false) {
    let path = path
    let maskLayer = CAShapeLayer()

    if inverse {
        path.append(UIBezierPath(rect: self.bounds))
        maskLayer.fillRule = kCAFillRuleEvenOdd
    }

    maskLayer.path = path.cgPath

    self.layer.mask = maskLayer
}

}

Наконец, вы можете использовать его так в ViewController ViewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()

   let hexImageView = HexagonImageView()
    hexImageView.image = UIImage(named: "hotcube")
    hexImageView.sideLength = 100

    let tap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
    tap.numberOfTapsRequired = 1
    hexImageView.isUserInteractionEnabled = true
    hexImageView.addGestureRecognizer(tap)

    view.addSubview(hexImageView)
}

func imageTapped() {
    print("tapped")
}
person Jonathan Hoche    schedule 02.01.2018

Я не уверен, что это самый простой и правильный способ, но я бы проверил местоположение касания пользователя и переопределил touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) .

person lithium    schedule 23.10.2015
comment
Функция touchesBegan(_:withEvent:) используется при реализации обработки жестов в подклассе UIResponder. Похоже, что OP использует файл UIGestureRecognizer. - person Charles A.; 23.10.2015