Search code examples
iosborderswift4xcode9uisegmentedcontrol

Adding custom border to UISegmentControl


I'm trying to customize my segement control like below image. So, far I was able to customize its text attributes and color. Only problem is with the border. As per the below image, if my first segment is selected the border should apply to first segment top, right and second segment's bottom. And if my second segment is selected it should be the reverse ie, second segment top, left and first segments bottom.

Segment Model Image

Things done so far

UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.blue], for: .selected)

UISegmentedControl.appearance().setTitleTextAttributes([NSAttributedStringKey.foregroundColor: UIColor.green], for: .normal)

Solution

  • You can do this by adding an extension to UISegmentedControl. Try this.

     extension UISegmentedControl {
    
    
      private func defaultConfiguration(font: UIFont = UIFont.boldSystemFont(ofSize: 12), color: UIColor = UIColor.gray) {
            let defaultAttributes = [
                NSAttributedStringKey.font.rawValue: font,
                NSAttributedStringKey.foregroundColor.rawValue: color
            ]
            setTitleTextAttributes(defaultAttributes, for: .normal)
        }
    
       private func selectedConfiguration(font: UIFont = UIFont.boldSystemFont(ofSize: 12), color: UIColor = UIColor.blue) {
            let selectedAttributes = [
                NSAttributedStringKey.font.rawValue: font,
                NSAttributedStringKey.foregroundColor.rawValue: color
            ]
            setTitleTextAttributes(selectedAttributes, for: .selected)
        }
    
    
       private func removeBorder(){
    
            let backgroundImage = getColoredRectImageWith(color: UIColor.white.cgColor, andSize: CGSize(width: self.bounds.size.width, height: self.bounds.size.height), yOffset: 2)  
            let backgroundImage2 = getColoredRectImageWith(color: UIColor.lightGray.cgColor, andSize: CGSize(width: self.bounds.size.width, height: self.bounds.size.height))
    
            self.setBackgroundImage(backgroundImage2, for: .normal, barMetrics: .default)
            self.setBackgroundImage(backgroundImage, for: .selected, barMetrics: .default)
            self.setBackgroundImage(backgroundImage, for: .highlighted, barMetrics: .default)
    
            let deviderImage = getColoredRectImageWith(color: UIColor.gray.cgColor, andSize: CGSize(width: 1.0, height: self.bounds.size.height))
            self.setDividerImage(deviderImage, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default)
    
            defaultConfiguration( color: UIColor.green)
            selectedConfiguration(color: UIColor.blue)
    
        }
    
        func addUnderlineForSelectedSegment(){
            removeBorder()
            let underlineWidth: CGFloat = self.bounds.size.width / CGFloat(self.numberOfSegments)
            let underlineHeight: CGFloat = 1.0
            let underlineXPosition = CGFloat(selectedSegmentIndex * Int(underlineWidth))
            let underLineYPosition = self.bounds.size.height - 2.0
            let underlineFrame = CGRect(x: underlineXPosition, y: underLineYPosition, width: underlineWidth, height: underlineHeight)
    
            let topUnderline = UIView(frame: underlineFrame)
            topUnderline.backgroundColor = UIColor.gray
            topUnderline.tag = 1
            topUnderline.frame.origin.y = self.frame.origin.y
            self.addSubview(topUnderline)
    
            let bottomUnderline = UIView(frame: underlineFrame)
            bottomUnderline.backgroundColor = UIColor.gray
            bottomUnderline.tag = 2
            bottomUnderline.frame.origin.x = topUnderline.frame.maxX
            self.addSubview(bottomUnderline)
        }
    
        func changeUnderlinePosition(){
    
    
            guard let topUnderline = self.viewWithTag(1) else {return}
            let topUnderlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(selectedSegmentIndex)
            topUnderline.frame.origin.x = topUnderlineFinalXPosition
    
            guard let bottomUnderline = self.viewWithTag(2) else {return}
            let underlineFinalXPosition = (selectedSegmentIndex == 0) ? topUnderline.frame.maxX : self.frame.origin.x
            bottomUnderline.frame.origin.x = underlineFinalXPosition
    
        }
    
        private func getColoredRectImageWith(color: CGColor, andSize size: CGSize,yOffset:CGFloat = 0, hOffset:CGFloat = 0) -> UIImage{
            UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
            let graphicsContext = UIGraphicsGetCurrentContext()
            graphicsContext?.setFillColor(color)
            let rectangle = CGRect(x: 0.0, y: 0.0 + yOffset, width: size.width, height: size.height - hOffset)
            graphicsContext?.fill(rectangle)
            let rectangleImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return rectangleImage!
        }
    
    }
    

    Usage

    In viewDidLoad add

    mySegmentControl.addUnderlineForSelectedSegment()
    

    And in your segment control action use

    @IBAction func mySegmentControl(_ sender: UISegmentedControl) {
    
            mySegmentControl.changeUnderlinePosition()
        }