Search code examples
swiftgraphicstooltiplinepoint

How to show tooltip on a point click in swift


I am drawing the lines using the following code. The line edges have dots and i want to show the tooltip when user click on the end dots. The code snippet is below,

 override func drawRect(rect: CGRect) {
        // Drawing code
      UIColor.brownColor().set()
    let context = UIGraphicsGetCurrentContext()
    //CGContextSetLineWidth(context, 5)
    CGContextMoveToPoint(context, 50, 50)
    CGContextAddLineToPoint(context,100, 200)
    CGContextStrokePath(context)

    // now add the circle on at the line edges

        var point = CGPoint(x:50 , y:50)
        point.x -= 5.0/2
        point.y -= 5.0/2
        var circle = UIBezierPath(ovalInRect: CGRect(origin: point, size: CGSize(width: 5.0,height: 5.0)))
        circle.fill()

        point = CGPoint(x:100 , y:200)
        point.x -= 5.0/2
        point.y -= 5.0/2
        circle = UIBezierPath(ovalInRect: CGRect(origin: point, size: CGSize(width: 5.0,height: 5.0)))
        circle.fill()

    }

It is currently displaying the following image, enter image description here

and i want to show the tooltip like below, enter image description here

Does anybody have an idea how will i recognise that this particular dot is clicked and also how will i show the tooltip. I am looking for a solution in swift.


Solution

  • Define a struct to hold points you have to touch, and the text to show:

    struct TouchPoint {
        var point: CGPoint // touch near here to show a tooltip
        var tip: String // show this text when touched
    }
    

    then in the UIView subclass where you define drawRect, make somewhere to keep them:

    var touchPoints: [TouchPoint] = [] // where we have to touch and what tooltip to show
    

    drawRect can be called many times, so start fresh each time:

    override func drawRect(rect: CGRect) {
        touchPoints = []
        // ...
        // add a touchPoint for every place to touch
        touchPoints.append(TouchPoint(point: point, tip: "point 1"))
    }
    

    You need to detect taps on the UIView, so add a gesture recognizer by changing its initialisation:

    required init?(coder aDecoder: NSCoder) {
        // standard init for a UIView wiht an added gesture recognizer
        super.init(coder: aDecoder)
        addGestureRecognizer(UITapGestureRecognizer(target: self, action: Selector("touched:")))
    }
    

    then you need a method to see whether touches are near touchpoints, and to show the right tooltip:

    func touched(sender:AnyObject) {
        let tapTolerance = CGFloat(20) // how close to the point to touch to see tooltip
        let tipOffset = CGVector(dx: 10, dy: -10) // tooltip offset from point
        let myTag = 1234 // random number not used elsewhere
        guard let tap:CGPoint = (sender as? UITapGestureRecognizer)?.locationInView(self) else { print("touched: failed to find tap"); return }
        for v in subviews where v.tag == myTag { v.removeFromSuperview() } // remove existing tooltips
        let hitPoints:[TouchPoint] = touchPoints.filter({CGPointDistance($0.point, to: tap) < tapTolerance}) // list of tooltips near to tap
        for h in hitPoints { // for each tooltip to show
            let f = CGRect(origin: h.point+tipOffset, size: CGSize(width: 100, height: 20)) // fixed size label :-(
            let l = UILabel(frame: f)
            l.tag = myTag // just to be able to remove the tooltip later
            l.text = h.tip // draw the text
            addSubview(l) // add the label to the view
        }
    }
    
    func CGPointDistanceSquared(from: CGPoint, to: CGPoint) -> CGFloat { return (from.x - to.x) * (from.x - to.x) + (from.y - to.y) * (from.y - to.y) }
    
    func CGPointDistance(from: CGPoint, to: CGPoint) -> CGFloat { return sqrt(CGPointDistanceSquared(from, to: to)) }
    

    and that incidentally uses a new version of the + operator to perform vector addition on CGPoint:

    func +(left: CGPoint, right: CGVector) -> CGPoint { return CGPoint(x: left.x+right.dx, y: left.y+right.dy) }
    

    and that works OK for me. Extra tweaks would be to compute the UILabel size from the text string, and move the UILabel so it didn't run off the side of the UIView at the edges. Good Luck!