22

How do I remove the outside border of a segmented control? I've set the divider image to what I wanted but now to follow the mock of my app I need to have a segmented control without the outer border.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Happiehappie
  • 1,084
  • 2
  • 13
  • 26

9 Answers9

71

What you must understand is the backgroundColor property is not stateful. Hence you have to use setBackgroundImage(_:for:barMetrics:).

We can easily remove both borders and dividers using the below function.

For Swift 3 & 4+:

extension UISegmentedControl {
    func removeBorders() {
        setBackgroundImage(imageWithColor(color: backgroundColor ?? .clear), for: .normal, barMetrics: .default)
        setBackgroundImage(imageWithColor(color: tintColor!), for: .selected, barMetrics: .default)
        setDividerImage(imageWithColor(color: UIColor.clear), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
    }

    // create a 1x1 image with this color
    private func imageWithColor(color: UIColor) -> UIImage {
        let rect = CGRect(x: 0.0, y: 0.0, width:  1.0, height: 1.0)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()
        context!.setFillColor(color.cgColor);
        context!.fill(rect);
        let image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return image!
    }
}

For Swift 2.2:

extension UISegmentedControl {
    func removeBorders() {
        setBackgroundImage(imageWithColor(backgroundColor!), forState: .Normal, barMetrics: .Default)
        setBackgroundImage(imageWithColor(tintColor!), forState: .Selected, barMetrics: .Default)
        setDividerImage(imageWithColor(UIColor.clearColor()), forLeftSegmentState: .Normal, rightSegmentState: .Normal, barMetrics: .Default)
    }

    // create a 1x1 image with this color
    private func imageWithColor(color: UIColor) -> UIImage {
        let rect = CGRectMake(0.0, 0.0, 1.0, 1.0)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()
        CGContextSetFillColorWithColor(context, color.CGColor);
        CGContextFillRect(context, rect);
        let image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return image
    }
}

Call the above function.

segmentedControl.removeBorders()

Reference: Remove UISegmentedControl separators completely. (iphone)

Thanks to https://stackoverflow.com/users/3921490/amagain for Swift 3 version.

Sohil R. Memon
  • 9,404
  • 1
  • 31
  • 57
  • Thanks :) It worked =D But one more question, this will make my segmented control have a sharp rectangular corner, any ways to make it rounded? – Happiehappie Jul 28 '15 at 03:33
  • @Happiehappie no you can't, please refer to this answer http://stackoverflow.com/questions/1834244/uisegmentedcontrol-without-rounded-corner – Sohil R. Memon Jul 28 '15 at 06:51
  • I am getting `fatal error: unexpectedly found nil while unwrapping an Optional value` on line `setBackgroundImage(imageWithColor(color: backgroundColor!), for: .normal, barMetrics: .default)` – Kakaji May 30 '17 at 09:33
  • You also need to set background color to some color other that `Default` apparently. `backgroundColor` was nil when it was set to `Default`. – Kakaji May 30 '17 at 09:39
  • 3
    To fix the background nil issue: `setBackgroundImage(imageWithColor(color: backgroundColor ?? .white), for: .normal, barMetrics: .default)` – Whoa Oct 18 '17 at 13:48
  • Doesn't work anymore on iOS13! Have a look at this question - https://stackoverflow.com/questions/57450865/uisegmentedcontrol-ios-13-clear-color – Deepak Sharma Aug 11 '19 at 14:46
12

Here's the swift 3 version of Sohil's answer that might help someone else. It did help me. :)

extension UISegmentedControl {
func removeBorders() {
    setBackgroundImage(imageWithColor(color: backgroundColor!), for: .normal, barMetrics: .default)
    setBackgroundImage(imageWithColor(color: tintColor!), for: .selected, barMetrics: .default)
    setDividerImage(imageWithColor(color: UIColor.clear), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
}

// create a 1x1 image with this color
private func imageWithColor(color: UIColor) -> UIImage {
    let rect = CGRect(x: 0.0, y: 0.0, width:  1.0, height: 1.0)
    UIGraphicsBeginImageContext(rect.size)
    let context = UIGraphicsGetCurrentContext()
    context!.setFillColor(color.cgColor);
    context!.fill(rect);
    let image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image!
    }
}
Community
  • 1
  • 1
amagain
  • 2,042
  • 2
  • 20
  • 36
6

Hope it will help someone

Swift - 4

Make the Background color and Tint color of your Segment control to same color. Then "set titleTextAttributes" of your Segment control

    segmentedControl.tintColor = UIColor.red
    segmentedControl.backgroundColor = UIColor.red
    let attributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
    segmentedControl.setTitleTextAttributes(attributes, for: .normal)
    segmentedControl.setTitleTextAttributes(attributes, for: .selected)
kalpa
  • 884
  • 1
  • 15
  • 22
2

If you want save borders between cells

extension UISegmentedControl {
  func removeBorders() {
    if let backgroundColor = backgroundColor, let backgroundImage = UIImage.imageWithSize(size: CGSize.one_one, color: backgroundColor){
      setBackgroundImage(backgroundImage, for: .normal, barMetrics: .default)
    }

    if let tintColor = tintColor, let tintImage = UIImage.imageWithSize(size: CGSize.one_one, color: tintColor){
      setBackgroundImage(tintImage, for: .selected, barMetrics: .default)
        setDividerImage(tintImage, forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
    }
  }
}

extension CGSize{
  static var one_one: CGSize{
    return CGSize(width: 1.0, height: 1.0)
  }
}

extension UIImage{
  static func imageWithSize(size : CGSize, color : UIColor = UIColor.white) -> UIImage? {
    var image:UIImage? = nil
    UIGraphicsBeginImageContext(size)
    if let context = UIGraphicsGetCurrentContext() {
      context.setFillColor(color.cgColor)
      context.addRect(CGRect(origin: CGPoint.zero, size: size));
      context.drawPath(using: .fill)
      image = UIGraphicsGetImageFromCurrentImageContext();
    }
    UIGraphicsEndImageContext()
    return image
  }
}
Leonardo Alves Machado
  • 2,747
  • 10
  • 38
  • 53
miss Gbot
  • 363
  • 3
  • 9
0

You might have the usecase where you don't want the rounded border as you don't want to see it if it's embedded in a cell. In this case, set the autolayout constraints to -2 and the border will be hidden as it will extend beyond the cell border.

AlexK
  • 336
  • 8
  • 21
  • 41
0

If you need segment control with texts Only, use,

segmentControl.backgroundColor = .clear
segmentControl.tintColor = .clear

let attributes: [NSAttributedString.Key : Any] = [.font : UIFont(family: .medium, ofSize: 13)!, .foregroundColor : UIColor.white]
segmentControl.setTitleTextAttributes(attributes, for: .normal)

let selectedAttrib: [NSAttributedString.Key : Any] = [.font : UIFont(family: .medium, ofSize: 13)!, .foregroundColor : UIColor.red]
segmentControl.setTitleTextAttributes(hightLightedStateAttrib, for: .selected)

NB: I googled it for a long time, thats why i'm posted here.

Source: CodeMentor

Lal Krishna
  • 15,485
  • 6
  • 64
  • 84
0
  • The accepted answer works for getting rid of the border.
  • The solution mentioned here also works for disabling the segmented Control.

Yet putting the two solutions together, I wasn't able to get the correct selected segment in a disabled state.

What I wanted was that upon selecting a segment and making a network call, I wanted to disable the segmented control.

The outcome was like this:

enter image description here

What was desired was this:

enter image description here

The only addition to Sohil's solution is:

setBackgroundImage(imageWithColor(color: imageWithColor(color: tintColor)), for: [.selected, .disabled], barMetrics: .default)

After the selected && disabled segment will have the right color.

mfaani
  • 33,269
  • 19
  • 164
  • 293
0

Swift 5.x:

This solution remove only the external border and preserve the round corner on each button

extension UISegmentedControl {
    func removeBorders(andBackground:Bool=false) {
        setBackgroundImage(imageWithColor(color: backgroundColor ?? .clear), for: .normal, barMetrics: .default)
        setBackgroundImage(imageWithColor(color: tintColor!), for: .selected, barMetrics: .default)
        setDividerImage(imageWithColor(color: UIColor.clear), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)

        _ = self.subviews.compactMap {
            if ($0.frame.width>0) {
                $0.layer.cornerRadius = 8
                $0.layer.borderColor = UIColor.clear.cgColor
                $0.clipsToBounds = true
                $0.layer.borderWidth = andBackground ? 1.0 : 0.0
                $0.layer.borderColor = andBackground ? tintColor?.cgColor : UIColor.clear.cgColor
                andBackground ? $0.layer.backgroundColor = UIColor.clear.cgColor : nil
            }
        }
    }

    // create a 1x1 image with this color
    private func imageWithColor(color: UIColor) -> UIImage {
        let rect = CGRect(x: 0.0, y: 0.0, width:  1.0, height: 1.0)
        UIGraphicsBeginImageContext(rect.size)
        let context = UIGraphicsGetCurrentContext()
        context!.setFillColor(color.cgColor);
        context!.fill(rect);
        let image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return image!
    }
}

Usage:

mySegmentedControl.removeBorders()
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
0

Sohil's answer did help me.

But if you don't set the UIImage size, it may affect the size of the UISegmentedControl.

If you also encounter this problem, please consider using the following code.

func *(lhs: CGSize, rhs: CGFloat) -> CGSize {
    return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
}

extension UIImage {
    func resized(_ size: CGSize) -> UIImage? {
        defer { UIGraphicsEndImageContext() }
        UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
        self.draw(in: CGRect(origin: .zero, size: size))
        return UIGraphicsGetImageFromCurrentImageContext()
    }

    func resized(_ size: CGFloat) -> UIImage? {
        guard size > 0, self.size.width > 0, self.size.height > 0 else { return nil }
        let ratio = size / max(self.size.width, self.size.height)
        return self.resized(self.size * ratio)
    }

    func refilled(_ color: UIColor) -> UIImage? {
        defer { UIGraphicsEndImageContext() }
        UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
        color.set()
        UIGraphicsGetCurrentContext()?.fill(CGRect(origin: .zero, size: self.size))
        return UIGraphicsGetImageFromCurrentImageContext()
    }

    public convenience init?(color: UIColor = .clear, size: CGSize) {
        defer { UIGraphicsEndImageContext() }
        UIGraphicsBeginImageContext(size)

        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        context.setFillColor(color.cgColor);
        context.fill(CGRect(origin: .zero, size: size));
        guard let cgImage = UIGraphicsGetImageFromCurrentImageContext()?.cgImage else { return nil }

        self.init(cgImage: cgImage)
    }
}

extension UISegmentedControl {
    func removeBorders() {
        let image = UIImage(size: self.bounds.size)
        self.setBackgroundImage(image, for: .normal, barMetrics: .default)
        self.setBackgroundImage(image?.refilled(self.tintColor), for: .selected, barMetrics: .default)
        self.setDividerImage(image?.resized(1), forLeftSegmentState: .normal, rightSegmentState: .normal, barMetrics: .default)
    }
}

Easy to use:

segmentedControl.removeBorders()
jqgsninimo
  • 6,562
  • 1
  • 36
  • 30