2

I'm trying to create a custom segmented control that sits underneath my search controller as seen in the mock up below

Segmented Control Mockup

Problem:

I'm having difficulty with creating the pointy needle (looks like this: "^") that indicates the current index.

Attempt at a solution:

Thanks to some help from the question below, I was able to get it looking close, but I'm unable to get the pointer to show up

https://stackoverflow.com/a/37705692/5254240

enter image description here

Question:

How do I get my code to look like the mockup with what I currently have and get the pointer to move with the current index of the segmented controller? See my code below

func imageWithColor(color: UIColor) -> UIImage {

    let rect = CGRectMake(0.0, 0.0, 1.0, segmentedController.frame.size.height)
    UIGraphicsBeginImageContext(rect.size)
    let context = UIGraphicsGetCurrentContext()
    CGContextSetFillColorWithColor(context, color.CGColor);
    CGContextFillRect(context, rect);
    let image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image

}

func initializeSearchController() {
    segmentedController.setTitleTextAttributes([NSFontAttributeName:UIFont(name: "Lato-Regular", size: 14)!,NSForegroundColorAttributeName:UIColor.init(red: 143/255, green: 142/255, blue: 148/255, alpha: 1.0)], forState:UIControlState.Normal)

    segmentedController.setTitleTextAttributes([NSFontAttributeName:UIFont(name: "Lato-Bold", size: 14)!,NSForegroundColorAttributeName:UIColor.init(red: 93/255, green: 176/255, blue: 175/255, alpha: 1.0)], forState:UIControlState.Selected)

    segmentedController.setDividerImage(self.imageWithColor(UIColor.clearColor()), forLeftSegmentState: UIControlState.Normal, rightSegmentState: UIControlState.Normal, barMetrics: UIBarMetrics.Default)

    segmentedController.setBackgroundImage(self.imageWithColor(UIColor.clearColor()), forState:UIControlState.Normal, barMetrics:UIBarMetrics.Default)

    segmentedController.setBackgroundImage(self.imageWithColor(UIColor.clearColor()), forState:UIControlState.Selected, barMetrics:UIBarMetrics.Default)

    segmentedController.setTitle("Search", forSegmentAtIndex: 0)
    segmentedController.setTitle("Meals", forSegmentAtIndex: 1)

    for  borderview in segmentedController.subviews {

        let upperBorder: CALayer = CALayer()
        upperBorder.backgroundColor = UIColor.init(red: 93/255, green: 176/255, blue: 175/255, alpha: 1.0).CGColor
        upperBorder.frame = CGRectMake(0, borderview.frame.size.height-1, borderview.frame.size.width, 1.0);
        borderview.layer.addSublayer(upperBorder);

    }

}
Community
  • 1
  • 1
Obi Anachebe
  • 123
  • 1
  • 13

1 Answers1

2

The easiest way to do this is to cheat. Instead of drawing the line with a bezier curve, you can just draw a solid line by using a UIView with a height of 1. Make the triangle with the solid white bottom in PhotoShop as an image and put it on top of the line. Now to animate the position you can either change its transform, update its center or use autolayout constraints and it will slide over into the new position. For example:

UIView.animate(withDuration: 0.25) {
    triangeImageView.center.x = selectedLabel.center.x
}

You can, of course also do this programmatically using a CAShapeLayer. You would construct your path use something along the lines of:

let bezierPath = UIBezierPath()
bezierPath.move(to:  CGPoint(x: left, y: bottom))
bezierPath.addLine(to: CGPoint(x: centerX + triangleWidth/2, y: top))
bezierPath.addLine(to: CGPoint(x: centerX, y: top - triangleHeight))
bezierPath.addLine(to: CGPoint(x: centerX - triangleWidth/2, y: top))
bezierPath.addLine(to: CGPoint(x: left, y: top))
bezierPath.close()

Then you can use a CABasicAnimation to animate the path from the old path to the new one. Since you are constructing the points in the same order the animation system will interpolate the position and the triangle will appear to slide into position.

Josh Homann
  • 15,933
  • 3
  • 30
  • 33
  • Thanks, Josh. So I went ahead and attempted to draw the line with a bezeirPath to see if I can get it to work programmatically, but I'm unsure of how to add the CAShapeLayer to my segmented control. After I create the path, how do I get it to "show up" in the segmented control? – Obi Anachebe Nov 10 '16 at 00:01
  • You need to create your layer in viewDidLayoutSubviews so that it is always sized correctly. (ie if the layer exists, removeFromSuperlayer and make a new CAShape layer) set the shapeLayer.path = bezierPath.cgPath. You also need to set the lineWidth and stroke color. Finally add your shape layer as a sublayer to your segmented control's view.layer. If you aren't familiar with layers its easier to do it using the first method i described with only UIView. – Josh Homann Nov 10 '16 at 00:24
  • This was a brilliant solution! I went with the approach of using a UIView with a white bottom border. Thanks for all the help, Josh! – Obi Anachebe Nov 10 '16 at 02:33