Help us understand the problem. What is going on with this article?

UIViewをくり抜く

More than 5 years have passed since last update.

UIViewがuserInteractionEnabled = trueの時は、レスポンダーチェーン的にそこでチェーンが止まり、その下にあるビューにタップが伝わらない。そこで、UIViewの一部のタップを無効にしてその下にあるビューにタップを伝えたい。どうすればよいか?

タップ無効領域を作る

UIViewのhitTest:withEvent:というメソッドを作って、タップ反応領域はselfを返す。タップ無効領域はnilを返すということで、簡単にUIViewをくり抜くことができます。ちなみに、今回、タップ無効領域はUIBezierPathを使って作成しています。UIBezierPathはcontainsPoint:というメソッドを持っていて、領域判定にこちらを使うとかなり便利です。

HollowView.swift
import UIKit

class HollowView: UIView {

    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        let radius = 100.0 as CGFloat
        let path = UIBezierPath(ovalInRect: self.bounds)
        if path.containsPoint(point) {
            return nil
        }
        return self
    }
}

試してみると、真ん中の円の部分だけタップは反応するけれど、四隅はタップがUIViewのところで止まっているのが確認できると思います。ただ、これだと見た目的にはわかりづらいですね。繰り抜かれた感じのイメージを置くのも良いですが、そもそもイメージごと繰り抜いてしまいたくなります。

見た目的にもくり抜く

見た目的にもくり抜くのは簡単には行きません。例としてUITableViewの上に、 HollowView と名づけた四角いビューを置き、その中心部分を丸く繰り抜きます。

HollowView.swift
import UIKit

class HollowView: UIView {

    var hollowRadius = 60.0 as CGFloat
    lazy var hollowPoint: CGPoint = {
        return CGPoint(
            x: CGRectGetWidth(self.bounds) / 2.0,
            y: CGRectGetHeight(self.bounds) / 2.0
        )
        }()

    lazy var hollowLayer: CALayer = {
        // 繰り抜きたいレイヤーを作成する(今回は例として半透明にした)
        let hollowTargetLayer = CALayer()
        hollowTargetLayer.bounds = self.bounds
        hollowTargetLayer.position = CGPoint(
            x: CGRectGetWidth(self.bounds) / 2.0,
            y: CGRectGetHeight(self.bounds) / 2.0
        )
        hollowTargetLayer.backgroundColor = UIColor.blackColor().CGColor
        hollowTargetLayer.opacity = 0.5

        // 四角いマスクレイヤーを作る
        let maskLayer = CAShapeLayer()
        maskLayer.bounds = hollowTargetLayer.bounds

        // 塗りを反転させるために、pathに四角いマスクレイヤーを重ねる
        let ovalRect =  CGRect(
            x: self.hollowPoint.x - self.hollowRadius,
            y: self.hollowPoint.y - self.hollowRadius,
            width: self.hollowRadius * 2.0,
            height: self.hollowRadius * 2.0
        )
        let path =  UIBezierPath(ovalInRect: ovalRect)
        path.appendPath(UIBezierPath(rect: maskLayer.bounds))

        maskLayer.fillColor = UIColor.blackColor().CGColor
        maskLayer.path = path.CGPath
        maskLayer.position = CGPoint(
            x: CGRectGetWidth(hollowTargetLayer.bounds) / 2.0,
            y: CGRectGetHeight(hollowTargetLayer.bounds) / 2.0
        )
        // マスクのルールをeven/oddに設定する
        maskLayer.fillRule = kCAFillRuleEvenOdd
        hollowTargetLayer.mask = maskLayer
        return hollowTargetLayer
    }()

    override func awakeFromNib() {
        super.awakeFromNib()
        self.backgroundColor = UIColor.clearColor()
    }

    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        let rect = CGRect(
            x: self.hollowPoint.x - self.hollowRadius,
            y: self.hollowPoint.y - self.hollowRadius,
            width: self.hollowRadius * 2.0,
            height: self.hollowRadius * 2.0
        )
        let hollowPath = UIBezierPath(roundedRect: rect, cornerRadius: self.hollowRadius)
        if !CGRectContainsPoint(self.bounds, point) || hollowPath.containsPoint(point) {
            return nil
        }
        return self
    }

    override func layoutSublayersOfLayer(layer: CALayer!) {
        layer.addSublayer(self.hollowLayer)
    }
}

こんなものができました。

hollow 2.png

hitTest:withEventのところでは、丸の中と、HollowViewの外側のタップを有効にしています。ポイントは、maskLayerの kCAFillRuleEvenOdd だと思います。even/oddルールということでUIBezierPathの重なりから、塗りと塗りではない部分を判定して処理してくれます。

サンプルコード

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした