We will create a button called “Show Tooltip” and when it is clicked, we will show the tooltip right above the button.
Let’s start by creating a project and name it as ToolTips.
Press cmd + N to create a new file. Select the ‘Cocoa Touch class’
and click ‘Next’ and enter the name of the file as ‘ToolTipView’ and make it a subclass of UIView class.
In the ToolTipView class, let us create an enum that represents the position of the tooltip. In this example, we will have 3 positions defined for tooltip.
enum ToolTipPosition: Int { case left case right case middle }
We now have our enum and let’s continue ahead by creating few properties for our tooltip.
var roundRect:CGRect!let toolTipWidth : CGFloat = 20.0let toolTipHeight : CGFloat = 12.0let tipOffset : CGFloat = 20.0var tipPosition : ToolTipPosition = .middle
- The var roundRect will store the reference to the actual CGRect that has the rounded rect bezier path that acts as the background for the text content.
- The var toolTipWidth and toolTipHeight are straightforward that references the tool tip width and height.
- We don’t want our tooltip to appear at the very beginning or very end of the view. Hence we need to give some offset so that tip alway be within the roundRect and look nice.
- By default, let us keep the tip position in the middle.
At this point, we have created the class with some vars. We now need to create a custom init method for our custom UIView class.
convenience init(frame: CGRect, text : String, tipPos: ToolTipPosition){ self.init(frame: frame) self.tipPosition = tipPos}
We have the method that init the required vars. It is now time to do some drawing. If you are not familiar with the bezier-paths, do not afraid. They are not as scary as you think.
For our tooltip, we need two bezier paths. One is the top rounded rectangle path. The second one is the bottom triangle path. For the first one,
roundRect = CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height — toolTipHeight)let roundRectBez = UIBezierPath(roundedRect: roundRect, cornerRadius: 5.0)
For the triangle path:
func createTipPath() -> UIBezierPath{ let tooltipRect = CGRect(x: getX(), y: roundRect.maxY, width: toolTipWidth, height: toolTipHeight) let trianglePath = UIBezierPath() trianglePath.move(to: CGPoint(x: tooltipRect.minX, y: tooltipRect.minY)) trianglePath.addLine(to: CGPoint(x: tooltipRect.maxX, y: tooltipRect.minY)) trianglePath.addLine(to: CGPoint(x: tooltipRect.midX, y: tooltipRect.maxY)) trianglePath.addLine(to: CGPoint(x: tooltipRect.minX, y: tooltipRect.minY)) trianglePath.close() return trianglePath}
Now we need to combine these two paths into one. I created a method that does it.
func drawToolTip(_ rect : CGRect){ roundRect = CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height — toolTipHeight) let roundRectBez = UIBezierPath(roundedRect: roundRect, cornerRadius: 5.0) let trianglePath = createTipPath() roundRectBez.append(trianglePath) let shape = createShapeLayer(roundRectBez.cgPath) self.layer.insertSublayer(shape, at: 0)}func createShapeLayer(_ path : CGPath) -> CAShapeLayer{ let shape = CAShapeLayer() shape.path = path shape.fillColor = UIColor.darkGray.cgColor shape.shadowColor = UIColor.black.withAlphaComponent(0.60).cgColor shape.shadowOffset = CGSize(width: 0, height: 2) shape.shadowRadius = 5.0 shape.shadowOpacity = 0.8 return shape}
Every UIView class has a drawRect: method that we can use to perform the drawing related operations.
override func draw(_ rect: CGRect) { super.draw(rect) drawToolTip(rect)}
That’s it. We have created a custom class for our tooltip. It is time to check how this looks like. Let’s go back to our ViewController class, and add the following code:
@IBOutlet weak var showToolTipBtn : UIButton!@IBAction func showToolTipClicked(){ showToolTip()}func showToolTip() { let p = showToolTipBtn.center let tipWidth: CGFloat = 200 let tipHeight: CGFloat = 80 let tipX = p.x — tipWidth / 2 let tipY: CGFloat = p.y — 80 let tipView = ToolTipView(frame: CGRect(x: tipX, y: tipY, width: tipWidth, height: tipHeight), text: “Hello User! This is a sample tool tip”, tipPos: .middle) UIApplication.shared.keyWindow?.addSubview(tipView) performShow(tipView)}func performShow(_ v: UIView?) { v?.transform = CGAffineTransform(scaleX: 0.01, y: 0.01) UIView.animate(withDuration: 0.3, delay: 0.3, options: .curveEaseOut, animations: { v?.transform = .identity }) { finished in // do something once the animation finishes, put it here }}
Make sure you connect the outlets and IBActions in the storyboard. When you run the code and after you click the button, it should look something like this:
Wait, that looks like a tooltip view but where is the text in it? Yes , you caught me. Let’s go back to our custom class view and add this code.
func createLabel(_ text : String){ let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height — toolTipHeight)) label.text = text label.textColor = .white label.textAlignment = .center label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping addSubview(label)}
and update our convenience init
convenience init(frame: CGRect, text : String, tipPos: ToolTipPosition){ self.init(frame: frame) self.tipPosition = tipPos createLabel(text)}
Run the project again, and you will now see the text inside our tip view.