hitTest on iOS to identify the view being hit

HitTest on iOS


Use hitTest, to identify the view being tapped. The below method allows us to identify the view being selected.

We can use it, to identify the view from which we would like to hide the touch events or identify the view we are interested into.

Example: In the below case when we tap on Black View we can show an alert like "Black View tapped!" , if we tap on Red View , we can show an alert  "You are not allowed to tap on Red View" . 

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

}


Here is a simple implementation of the method, to identify the view being hit.




View hierarchy being used is listed below:-


Blue(parent)

- Red(child)

- Green(child)

- Yellow (child)

- Orange (child)

- Black (child)

- White (child)



So lets' start with override of hitTest method.



    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

        //1

        if let viewAvailable = findPointsInsideView(parent: self, point: point, event: event) {

            return viewAvailable

        }

        //2

        if self.point(inside: point, with: event) {

            return self

        }

        return nil

    }




1. We identify if the point is inside any of the sub view's of the main parent view.
Parent View in this case is the class which override the hitTest method.

2. If we don't receive any sub-view which includes the given point we check if the it's the parent view who actually received the hit.


Below is a helper method which traverses through the subview's to identify the if the point belongs to any of the subview.





    private func findPointsInsideView(parent: UIView, point: CGPoint, event: UIEvent?) -> UIView? {

        let allChilds = parent.subviews

        //1

        guard allChilds.count > 0 else {

            return nil

        }

        //2

        for babyView in allChilds.reversed() {

        //3

            let modifiedPoint = parent.convert(point, to: babyView)

        //4

            if babyView.point(inside: modifiedPoint, with: event){

        //5

                if let foundView = findPointsInsideView(parent: babyView, point: modifiedPoint, event: event) {

                    return foundView

                } else {

                    return babyView

                }

            }

        }

        return nil

    }






1. Check if the parent has further child view's or not. This is usually useful if the view receiving the touch is at the end in the hierarchy.

If the count of subview's is 0 , there is no point in traversing further.

In the above case if we tap on Black View , it does not traverse further.



2. We traverse from the top most view in the hierarchy.
In current case for parent view subviews will be "Red View" ,"Green View" and the "White View".

So the reverse order of traversing would be "White View" -> "Green View" and "Red View"


This is because it could happen there would be overlap of the views ex: Overlapping of Green and Red View.


If we don't use reversed() function, tapping on Green area which overlaps with Red View, findPointsInsideView will return the RedView itself as the func point(inside point: CGPointwith event: UIEvent?) -> Bool , will turn true


This can be seen in below picture , where if we tap on brown scribbled area we should return Green View not the Red View.




3. Convert the point: CGPoint received from parent co-ordinates to it's child co-ordinate by using parent.convert(point, to: babyView)


ex: if we tap on some area in black view the point would be as shown below, it would be with reference to the parent view bounds (i.e the Blue view) i.e the point value is some point in Blue View.

(120.5, 318.0)

  - x : 120.5

  - y : 318.0


However for the blackView to check if the modifiedPoint is inside the babyView or not (i.e BlackView) we would need this translation.


modifiedPoint value will be


▿ (31.5, 9.0)

  - x : 31.5

  - y : 9.0


Bounds for babyView (i.e Black View) is let's say for example 0,0,80,20 , then yes certainly the point is inside the BlackView.


4. After the conversion the point translation from parent to child , we check if it actually lies inside the child(babyView).

If we find the view, that's the view hierarchy we are interested into. So keep diving into it.

5. We keep traversing through , till we receive a view. If we reach a dead end view (ex: black View), the value of foundView will be nil and we need to return the black View.

Comments

Popular posts from this blog

CMTimeMakeWithSeconds explained

Custom Editing Control for UITableViewCell , like iPhone Mail Appliation